라이브 Android 오디오를 서버로 스트리밍
현재 Android 장치에서 Java 프로그램으로 라이브 마이크 오디오를 스트리밍하려고합니다. 내 방법이 올바른지 확인하기 위해 두 개의 Android 장치간에 라이브 오디오를 보내는 것으로 시작했습니다. 오디오는 수신 장치에서 거의 지연없이 완벽하게 들릴 수 있습니다. 다음으로 동일한 오디오 스트림을 작은 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에 대한 경험이없고 실제로 사용 방법을 배울 시간이 없기 때문에 NDK를 사용하지 않는 것이 좋습니다 .... 아직 :)
그래서 내 문제를 해결했습니다. 문제는 주로 수신 측에있었습니다. 수신기는 오디오 스트림을 받아 PC 스피커로 밀어냅니다. 그 결과 음성은 여전히 상당히 느리고 끊어졌지만 덜 작동합니다. 버퍼 크기를 가지고 놀면 이것을 향상시킬 수 있습니다.
편집 : 지연을 피하기 위해 스레드를 사용하여 오디오를 읽습니다. 또한 음성에 적합하므로 16,000의 샘플링 크기를 사용하는 것이 좋습니다.
Android 코드 :
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_width="match_parent"
android:layout_height="match_parent"
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_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/hello_world" />
<Button
android:id="@+id/start_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/textView1"
android:layout_centerHorizontal="true"
android:layout_marginTop="130dp"
android:text="Start" />
<Button
android:id="@+id/stop_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
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(), sourceDataLine);
}
}).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();
}
}
}
나는 이것이 누군가에게 몇 시간의 고통을 덜어주기를 바랍니다. :)
My 2 cents to your code to improve the efficiency. Nice try
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();
}
}
}
The voice is broken because of the following line in your android code:
minBufSize += 2048;
You're just adding empty bytes. Also, use CHANNEL_IN_MONO
instead of CHANNEL_CONFIGURATION_MONO
ReferenceURL : https://stackoverflow.com/questions/15349987/stream-live-android-audio-to-server
'development' 카테고리의 다른 글
AutoMapper의 대안 (0) | 2020.12.25 |
---|---|
ggplot 범례에서 선 색상 및 선 유형 제어 (0) | 2020.12.25 |
컨볼 루션 신경망과 반복 신경망의 차이점은 무엇입니까? (0) | 2020.12.25 |
PHP 인터페이스에 속성이 있습니까? (0) | 2020.12.25 |
안드로이드를 지원하는 SQLite의 데이터 유형은 무엇입니까 (0) | 2020.12.25 |