将实时 Android 音频流式传输到服务器
Posted
技术标签:
【中文标题】将实时 Android 音频流式传输到服务器【英文标题】:Stream Live Android Audio to Server 【发布时间】:2013-02-27 07:15:42 【问题描述】:我目前正在尝试将实时麦克风音频从 android 设备流式传输到 Java 程序。我开始在两个安卓设备之间发送实时音频,以确认我的方法是正确的。在接收设备上几乎没有任何延迟,可以完美地听到音频。接下来,我将相同的音频流发送到一个小型 Java 程序,并且我验证了数据也被正确发送到这里。现在我想做的是对这些数据进行编码,并以某种方式在运行 Java 程序的服务器上播放它。我宁愿在使用 html5 或 javascript 的网络浏览器中播放它,但我对 VLC 等替代方法持开放态度。
这是发送实时麦克风音频的 Android 应用的代码
public class MainActivity extends Activity
private Button startButton,stopButton;
public byte[] buffer;
public static DatagramSocket socket;
AudioRecord recorder;
private int sampleRate = 44100;
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
private boolean status = true;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startButton = (Button) findViewById (R.id.start_button);
stopButton = (Button) findViewById (R.id.stop_button);
startButton.setOnClickListener(startListener);
stopButton.setOnClickListener(stopListener);
minBufSize += 2048;
@Override
public boolean onCreateOptionsMenu(Menu menu)
getMenuInflater().inflate(R.menu.main, menu);
return true;
private final OnClickListener stopListener = new OnClickListener()
@Override
public void onClick(View arg0)
status = false;
recorder.release();
Log.d("VS","Recorder released");
;
private final OnClickListener startListener = new OnClickListener()
@Override
public void onClick(View arg0)
status = true;
startStreaming();
;
public void startStreaming()
Thread streamThread = new Thread(new Runnable()
@Override
public void run()
try
DatagramSocket socket = new DatagramSocket();
Log.d("VS", "Socket Created");
byte[] buffer = new byte[minBufSize];
Log.d("VS","Buffer created of size " + minBufSize);
Log.d("VS", "Address retrieved");
recorder = new AudioRecord(MediaRecorder.Audiosource.MIC,sampleRate,channelConfig,audioFormat,minBufSize);
Log.d("VS", "Recorder initialized");
recorder.startRecording();
InetAddress IPAddress = InetAddress.getByName("192.168.1.5");
byte[] sendData = new byte[1024];
byte[] receiveData = new byte[1024];
while (status == true)
DatagramPacket sendPacket = new DatagramPacket(sendData, sendData.length, IPAddress, 50005);
socket.send(sendPacket);
catch(UnknownHostException e)
Log.e("VS", "UnknownHostException");
catch (IOException e)
Log.e("VS", "IOException");
e.printStackTrace();
);
streamThread.start();
这是Java程序读取数据的代码..
class Server
public static void main(String args[]) throws Exception
DatagramSocket serverSocket = new DatagramSocket(50005);
byte[] receiveData = new byte[1024];
byte[] sendData = new byte[1024];
while(true)
DatagramPacket receivePacket = new DatagramPacket(receiveData, receiveData.length);
serverSocket.receive(receivePacket);
String sentence = new String( receivePacket.getData().toString());
System.out.println("RECEIVED: " + sentence);
我知道在将音频发送到 Java 程序之前,我应该在应用端对音频进行编码,但我不确定在使用 AudioRecorder 时如何进行编码。我宁愿不使用 NDK,因为我没有使用它的经验,也没有真正有时间学习如何使用它......但是 :)
【问题讨论】:
你用什么流音频?我只想要一种方式。 我有兴趣编写一个 android 应用程序,将实时麦克风音频从 Android 设备流式传输到桌面应用程序。您能否提供一些有用的指针,指向与您发布的内容相关的一些资源?那将非常有帮助!谢谢! :) 你是如何在两台安卓设备之间实现实时音频的? 嘿@chuckliddell0,你能提供我在Android中发送者和接收者之间发送音频的源代码吗?这会很有帮助,因为我被困在这里。 【参考方案1】:所以我解决了我的问题。问题主要出在接收方。接收器接收音频流并将其推送到 PC 的扬声器。由此产生的声音仍然很迟钝和破碎,但它仍然有效。调整缓冲区大小可能会改善这一点。
编辑:您使用线程来读取音频以避免延迟。此外,最好使用 16 000 的采样大小,因为它可以用于语音。
安卓代码:
package com.example.mictest2;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.UnknownHostException;
import android.app.Activity;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
public class Send extends Activity
private Button startButton,stopButton;
public byte[] buffer;
public static DatagramSocket socket;
private int port=50005;
AudioRecord recorder;
private int sampleRate = 16000 ; // 44100 for music
private int channelConfig = AudioFormat.CHANNEL_CONFIGURATION_MONO;
private int audioFormat = AudioFormat.ENCODING_PCM_16BIT;
int minBufSize = AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat);
private boolean status = true;
@Override
public void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startButton = (Button) findViewById (R.id.start_button);
stopButton = (Button) findViewById (R.id.stop_button);
startButton.setOnClickListener (startListener);
stopButton.setOnClickListener (stopListener);
private final OnClickListener stopListener = new OnClickListener()
@Override
public void onClick(View arg0)
status = false;
recorder.release();
Log.d("VS","Recorder released");
;
private final OnClickListener startListener = new OnClickListener()
@Override
public void onClick(View arg0)
status = true;
startStreaming();
;
public void startStreaming()
Thread streamThread = new Thread(new Runnable()
@Override
public void run()
try
DatagramSocket socket = new DatagramSocket();
Log.d("VS", "Socket Created");
byte[] buffer = new byte[minBufSize];
Log.d("VS","Buffer created of size " + minBufSize);
DatagramPacket packet;
final InetAddress destination = InetAddress.getByName("192.168.1.5");
Log.d("VS", "Address retrieved");
recorder = new AudioRecord(MediaRecorder.AudioSource.MIC,sampleRate,channelConfig,audioFormat,minBufSize*10);
Log.d("VS", "Recorder initialized");
recorder.startRecording();
while(status == true)
//reading data from MIC into buffer
minBufSize = recorder.read(buffer, 0, buffer.length);
//putting buffer in the packet
packet = new DatagramPacket (buffer,buffer.length,destination,port);
socket.send(packet);
System.out.println("MinBufferSize: " +minBufSize);
catch(UnknownHostException e)
Log.e("VS", "UnknownHostException");
catch (IOException e)
e.printStackTrace();
Log.e("VS", "IOException");
);
streamThread.start();
Android XML:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_
android:layout_
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
tools:context=".MainActivity" >
<TextView
android:id="@+id/textView1"
android:layout_
android:layout_
android:text="@string/hello_world" />
<Button
android:id="@+id/start_button"
android:layout_
android:layout_
android:layout_below="@+id/textView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="130dp"
android:text="Start" />
<Button
android:id="@+id/stop_button"
android:layout_
android:layout_
android:layout_alignLeft="@+id/button1"
android:layout_below="@+id/button1"
android:layout_marginTop="64dp"
android:text="Stop" />
</RelativeLayout>
服务器代码:
package com.datagram;
import java.io.ByteArrayInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
class Server
AudioInputStream audioInputStream;
static AudioInputStream ais;
static AudioFormat format;
static boolean status = true;
static int port = 50005;
static int sampleRate = 44100;
public static void main(String args[]) throws Exception
DatagramSocket serverSocket = new DatagramSocket(50005);
byte[] receiveData = new byte[1280];
// ( 1280 for 16 000Hz and 3584 for 44 100Hz (use AudioRecord.getMinBufferSize(sampleRate, channelConfig, audioFormat) to get the correct size)
format = new AudioFormat(sampleRate, 16, 1, true, false);
while (status == true)
DatagramPacket receivePacket = new DatagramPacket(receiveData,
receiveData.length);
serverSocket.receive(receivePacket);
ByteArrayInputStream baiss = new ByteArrayInputStream(
receivePacket.getData());
ais = new AudioInputStream(baiss, format, receivePacket.getLength());
// A thread solve the problem of chunky audio
new Thread(new Runnable()
@Override
public void run()
toSpeaker(receivePacket.getData());
).start();
public static void toSpeaker(byte soundbytes[])
try
DataLine.Info dataLineInfo = new DataLine.Info(SourceDataLine.class, format);
SourceDataLine sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
sourceDataLine.open(format);
FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
volumeControl.setValue(100.0f);
sourceDataLine.start();
sourceDataLine.open(format);
sourceDataLine.start();
System.out.println("format? :" + sourceDataLine.getFormat());
sourceDataLine.write(soundbytes, 0, soundbytes.length);
System.out.println(soundbytes.toString());
sourceDataLine.drain();
sourceDataLine.close();
catch (Exception e)
System.out.println("Not working in speakers...");
e.printStackTrace();
我希望这可以帮助人们减少几个小时的痛苦:)
【讨论】:
这会产生持续的抖动并且声音不清晰。可以做什么? 让你通过服务器将你的语音流从一台安卓设备发送到多台安卓设备,就像群组对话一样。我正在努力学习它,但找不到任何东西。你能帮我吗? ? 我可以联系你吗?? 我发现在使用流式直播麦克风音频时,44.1KHz 对于采样率来说太高了。据我所见,8000 或 16000 的采样率效果更好。这与 android 文档中建议的 44100 采样率背道而驰,但这是我从经验中发现的。此外,请参阅en.wikipedia.org/wiki/Sampling_%28signal_processing%29,了解哪种采样率最适合您的应用。 @chuckliddell0 我正在开发同一个应用程序,我使用了你的代码,但问题是我的声音中有太多的回声,我不会流式传输通话音频,你能给我一些帮助吗?如果可能的话,你能提供你的联系方式吗?【参考方案2】:我的 2 美分给你的代码以提高效率。不错的尝试
package com.datagram;
import java.io.ByteArrayInputStream;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioInputStream;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.DataLine;
import javax.sound.sampled.FloatControl;
import javax.sound.sampled.SourceDataLine;
class Server
AudioInputStream audioInputStream;
static AudioInputStream ais;
static AudioFormat format;
static boolean status = true;
static int port = 50005;
static int sampleRate = 44100;
static DataLine.Info dataLineInfo;
static SourceDataLine sourceDataLine;
public static void main(String args[]) throws Exception
DatagramSocket serverSocket = new DatagramSocket(port);
/**
* Formula for lag = (byte_size/sample_rate)*2
* Byte size 9728 will produce ~ 0.45 seconds of lag. Voice slightly broken.
* Byte size 1400 will produce ~ 0.06 seconds of lag. Voice extremely broken.
* Byte size 4000 will produce ~ 0.18 seconds of lag. Voice slightly more broken then 9728.
*/
byte[] receiveData = new byte[4096];
format = new AudioFormat(sampleRate, 16, 1, true, false);
dataLineInfo = new DataLine.Info(SourceDataLine.class, format);
sourceDataLine = (SourceDataLine) AudioSystem.getLine(dataLineInfo);
sourceDataLine.open(format);
sourceDataLine.start();
FloatControl volumeControl = (FloatControl) sourceDataLine.getControl(FloatControl.Type.MASTER_GAIN);
volumeControl.setValue(1.00f);
DatagramPacket receivePacket = new DatagramPacket(receiveData,
receiveData.length);
ByteArrayInputStream baiss = new ByteArrayInputStream(
receivePacket.getData());
while (status == true)
serverSocket.receive(receivePacket);
ais = new AudioInputStream(baiss, format, receivePacket.getLength());
toSpeaker(receivePacket.getData());
sourceDataLine.drain();
sourceDataLine.close();
public static void toSpeaker(byte soundbytes[])
try
sourceDataLine.write(soundbytes, 0, soundbytes.length);
catch (Exception e)
System.out.println("Not working in speakers...");
e.printStackTrace();
【讨论】:
这真的有助于减少噪音吗? 这甚至在 android 中都行不通。 ***.com/questions/16803343/… 变量ais
和baiss
在代码中究竟做了什么?我可以看到它们都使用接收到的数据进行了初始化,但在 toSpeaker
中使用了原始字节......
如果此解决方案不适用于最新的 Android,请关注此帖子。 ***.com/questions/20193645/… 该帖子仅基于此方法,但作者修改了一些效果很好的更改。感谢两位作者 user1729564 和 kittu88【参考方案3】:
由于您的 android 代码中的以下行导致声音中断:
minBufSize += 2048;
您只是在添加空字节。另外,请使用CHANNEL_IN_MONO
而不是CHANNEL_CONFIGURATION_MONO
【讨论】:
以上是关于将实时 Android 音频流式传输到服务器的主要内容,如果未能解决你的问题,请参考以下文章
将音频和视频从 Android 手机流式传输到 RTMP 服务器的最佳方式
如何将音频数据从 Android 流式传输到 WebSocket 服务器?