Android下音频录制以及网络传输的思路及开发方法

本文来讲述在Android中使用AudioRecord和AudioTrack来进行音频录制播放,并使用speex进行音频压缩编码,再采用TCP传输思路和方法。

1 开篇

在之前的文章我讨论过视频压缩,网络传输,多播等方案和思路,所以考虑到全面的问题,我在这里开始讨论音频的压缩传输问题了。当然这篇文章也属于从网上总结而来的,里面也有一些自己的思考方式。特别感谢开源项目android-recorder,里面很多代码给我直接的帮助。敬佩。


2 相关知识

本文的相关知识为AudioRecord、AudioTrack、Thread、TCP、Speex等。如果有问题可以单独网上搜索这些关键字。

3 多线程音频读取思路

android-recorder项目中的两个线程处理方法非常好。一个线程启动录制,然后循环调用Read方法,并将数据压倒另外一个线程的数据处理队列中,在另外一个线程的run里面,循环从队列里面取出一条,并写入到文件里面。代码示例如下:
线程1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
public  void run ( )  {
PcmWriter pcmWriter  =  new PcmWriter ( ) ;
pcmWriter. init ( ) ;
Thread writerThread  =  new  Thread  (pcmWriter ) ;
pcmWriter. setRecording ( true ) ;
writerThread. start ( ) ;

synchronized  (mutex )  {
while  ( ! this. isRecording )  {
try  {
mutex. wait ( ) ;
}  catch  ( InterruptedException e )  {
throw  new  IllegalStateException ( "Wait() interrupted!", e ) ;
}
}
}
android. os. Process
. setThreadPriority (android. os. Process. THREAD_PRIORITY_URGENT_AUDIO ) ;

int bufferRead  =  0 ;
int bufferSize  = AudioRecord. getMinBufferSize (frequency,
AudioFormat. CHANNEL_IN_MONO, audioEncoding ) ;

short [ ] tempBuffer  =  new  short [bufferSize ] ;
AudioRecord recordInstance  =  new AudioRecord (
MediaRecorder. AudioSource. MIC, frequency,
AudioFormat. CHANNEL_IN_MONO, audioEncoding, bufferSize ) ;

recordInstance. startRecording ( ) ;

while  ( this. isRecording )  {

bufferRead  = recordInstance. read (tempBuffer,  0, bufferSize ) ;
if  (bufferRead  == AudioRecord. ERROR_INVALID_OPERATION )  {
throw  new  IllegalStateException (
"read() returned AudioRecord.ERROR_INVALID_OPERATION" ) ;
}  else  if  (bufferRead  == AudioRecord. ERROR_BAD_VALUE )  {
throw  new  IllegalStateException (
"read() returned AudioRecord.ERROR_BAD_VALUE" ) ;
}  else  if  (bufferRead  == AudioRecord. ERROR_INVALID_OPERATION )  {
throw  new  IllegalStateException (
"read() returned AudioRecord.ERROR_INVALID_OPERATION" ) ;
}

pcmWriter. putData (tempBuffer, bufferRead ) ;
log. debug ( "put data done!" ) ;

}
recordInstance. stop ( ) ;
pcmWriter. setRecording ( false ) ;
}

线程2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public  void run ( )  {
log. error ( "pcmwriter thread runing" ) ;
while  ( this. isRecording ( ) )  {

if (list. size ( )  &gt ;  0 ) {
rawData  = list. remove ( 0 ) ;
try  {
for  ( int i  =  0 ; i  &lt ; rawData. size ;  ++i )  {
dataOutputStreamInstance. writeShort (rawData. pcm [i ] ) ;
}
}  catch  ( IOException e )  {
e. printStackTrace ( ) ;
}
}  else  {
try  {
Thread. sleep ( 200 ) ;
}  catch  ( InterruptedException e )  {
e. printStackTrace ( ) ;
}
}

}
stop ( ) ;
}

上面看着代码很多,实际核心代码为:

1
2
3
4
5
6
7
8
9
10
11
short [ ] tempBuffer  =  new  short [bufferSize ] ;
AudioRecord recordInstance  =  new AudioRecord (
MediaRecorder. AudioSource. MIC, frequency,
AudioFormat. CHANNEL_IN_MONO, audioEncoding, bufferSize ) ;

recordInstance. startRecording ( ) ;
while  ( this. isRecording )  {
bufferRead  = recordInstance. read (tempBuffer,  0, bufferSize ) ;
pcmWriter. putData (tempBuffer, bufferRead ) ;
}
recordInstance. stop ( ) ;

这里需要解释一下几个问题:

Class Overview
The AudioRecord class manages the audio resources for Java applications to record audio from the audio input hardware of the platform. This is achieved by “pulling” (reading) the data from the AudioRecord object. The application is responsible for polling the AudioRecord object in time using one of the following three methods: read(byte[], int, int), read(short[], int, int) or read(ByteBuffer, int). The choice of which method to use will be based on the audio data storage format that is the most convenient for the user of AudioRecord.

Upon creation, an AudioRecord object initializes its associated audio buffer that it will fill with the new audio data. The size of this buffer, specified during the construction, determines how long an AudioRecord can record before “over-running” data that has not been read yet. Data should be read from the audio hardware in chunks of sizes inferior to the total recording buffer size.

AudioRecord类从音频输入硬件底层为Java应用程序管理音频资源。这是通过从AudioRecord对象读取数据。应用程序可以通过是那个read方法从AudioRecord对象读取数据。选择哪个读取方法根据音频数据储存格式决定。

创建的时候,AudioRecord对象需要提供一个音频数据buffer存放地址。

1
public AudioRecord  ( int audioSource,  int sampleRateInHz,  int channelConfig,  int audioFormat,  int bufferSizeInBytes )

Since: API Level 3 Class constructor.

Parameters
audioSource the recording source. See MediaRecorder.AudioSource for recording source definitions.
sampleRateInHz the sample rate expressed in Hertz. 44100Hz is currently the only rate that is guaranteed to work on all devices, but other rates such as 22050, 16000, and 11025 may work on some devices.
channelConfig describes the configuration of the audio channels. See CHANNEL_IN_MONO and CHANNEL_IN_STEREO. CHANNEL_IN_MONO is guaranteed to work on all devices.
audioFormat the format in which the audio data is represented. See ENCODING_PCM_16BIT and ENCODING_PCM_8BIT
bufferSizeInBytes the total size (in bytes) of the buffer where audio data is written to during the recording. New audio data can be read from this buffer in smaller chunks than this size. See getMinBufferSize(int, int, int) to determine the minimum required buffer size for the successful creation of an AudioRecord instance. Using values smaller than getMinBufferSize() will result in an initialization failure.

Throws
IllegalArgumentException

构造函数参数说明:
audioSource 录制源,参考 MediaRecorder.AudioSource的定义

int CAMCORDER Microphone audio source with same orientation as camera if available, the main device microphone otherwise
int DEFAULT
int MIC Microphone audio source
int VOICE_CALL Voice call uplink + downlink audio source
int VOICE_COMMUNICATION Microphone audio source tuned for voice communications such as VoIP.
int VOICE_DOWNLINK Voice call downlink (Rx) audio source
int VOICE_RECOGNITION Microphone audio source tuned for voice recognition if available, behaves like DEFAULT otherwise.
int VOICE_UPLINK Voice call uplink (Tx) audio source

sampleRateInHz 参数为抽样频率,目前 44100Hz 是支持所有设备的,其他的22050, 16000, and 11025可能在部分设备上好使。
audioFormat 音频格式,ENCODING_PCM_16BIT和 ENCODING_PCM_8BIT,第一种音频会清楚一些,也大一些。
bufferSizeInBytes 是一个预存储音频数据空间大小。

4 播放方法

1
2
3
4
5
6
7
8
9
10
AudioTrack mAudioTrack ;
mAudioTrack  =  new AudioTrack (AudioManager. STREAM_MUSIC,mFrequency,mChannel,mSampBit,minBufSize,AudioTrack. MODE_STREAM ) ;
//AudioTrack中有MODE_STATIC和MODE_STREAM两种分类。
//STREAM的意思是由用户在应用程序通过write方式把数据一次一次得写到audiotrack中。
//这个和我们在socket中发送数据一样,应用层从某个地方获取数据,例如通过编解码得到PCM数据,然后write到audiotrack。
//这种方式的坏处就是总是在JAVA层和Native层交互,效率损失较大。
//而STATIC的意思是一开始创建的时候,就把音频数据放到一个固定的buffer,然后直接传给audiotrack,
//后续就不用一次次得write了。AudioTrack会自己播放这个buffer中的数据。
//这种方法对于铃声等内存占用较小,延时要求较高的声音来说很适用。
mAudioTrack. play ( ) ;

上面提到,在AudioTrack构造函数的最后一个参数,有两种类型,有MODE_STATIC和MODE_STREAM,如果是播放PCM文件,一般采用MODE_STATIC方式,对于是实时Buffer方式,采用MODE_STREAM。

Class Overview
The AudioTrack class manages and plays a single audio resource for Java applications. It allows to stream PCM audio buffers to the audio hardware for playback. This is achieved by “pushing” the data to the AudioTrack object using one of the write(byte[], int, int) and write(short[], int, int) methods.

AudioTrack类为Java应用程序管理和播放单一音频资源。她支持PCM流音频缓冲到音频硬件的回放。回放是通过将数据通过write函数压入到AudioTrack对象中实现。
An AudioTrack instance can operate under two modes: static or streaming.

一个AudioTrack实例可以通过两种模式操作,静态和流的模式。

In Streaming mode, the application writes a continuous stream of data to the AudioTrack, using one of the write() methods. These are blocking and return when the data has been transferred from the Java layer to the native layer and queued for playback. The streaming mode is most useful when playing blocks of audio data that for instance are:

在流的模式,应用程序使用write方法将一个连续流数据写入到AudioTrack。流模式在下列的情况将被用到:

•too big to fit in memory because of the duration of the sound to play,
音频太大,不能一次性加载到内存播放
•too big to fit in memory because of the characteristics of the audio data (high sampling rate, bits per sample …)
由于音频数据的特征(高采样率,每采样一个位等)导致音频很大,不能加载到内存中播放
•received or generated while previously queued audio is playing.
网络接收到的实时音频播放
The static mode is to be chosen when dealing with short sounds that fit in memory and that need to be played with the smallest latency possible. The static mode will therefore be preferred for UI and game sounds that are played often, and with the smallest overhead possible.

在static模式,一般是比较小的音频。在游戏里面一般用到比较多。

Upon creation, an AudioTrack object initializes its associated audio buffer. The size of this buffer, specified during the construction, determines how long an AudioTrack can play before running out of data.
For an AudioTrack using the static mode, this size is the maximum size of the sound that can be played from it.
For the streaming mode, data will be written to the hardware in chunks of sizes inferior to the total buffer size.

AudioTrack对象创建的时候,需要初始化几个参数,包含音频Buffer,Buffer大小,对于static模式,最大的播放音频大小,对于流模式,为数据写入到总缓冲区的大小块

5 jni使用speex进行压缩编码
官方文档里面提到了,如果使用C/C++调用speex进行音频压缩及解压的代码。

压缩代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
#include
#include

/*The frame size in hardcoded for this sample code but it doesn't have to be*/
#define FRAME_SIZE 160
int main ( int argc ,  char  **argv )
{
char  *inFile ;
FILE  *fin ;
short in [FRAME_SIZE ] ;
float input [FRAME_SIZE ] ;
char cbits [ 200 ] ;
int nbBytes ;
/*Holds the state of the encoder*/
void  *state ;
/*Holds bits so they can be read and written to by the Speex routines*/
SpeexBits bits ;
int i , tmp ;

/*Create a new encoder state in narrowband mode*/
state  = speex_encoder_init ( &amp ;speex_nb_mode ) ;

/*Set the quality to 8 (15 kbps)*/
tmp = 8 ;
speex_encoder_ctl (state , SPEEX_SET_QUALITY ,  &amp ;tmp ) ;

inFile  = argv [ 1 ] ;
fin  =  fopen (inFile ,  "r" ) ;

/*Initialization of the structure that holds the bits*/
speex_bits_init ( &amp ;bits ) ;
while  ( 1 )
{
/*Read a 16 bits/sample audio frame*/
fread (in ,  sizeof ( short ) , FRAME_SIZE , fin ) ;
if  ( feof (fin ) )
break ;
/*Copy the 16 bits values to float so Speex can work on them*/
for  (i = 0 ;i input [i ] =in [i ] ;

/*Flush all the bits in the struct so we can encode a new frame*/
speex_bits_reset ( &amp ;bits ) ;

/*Encode the frame*/
speex_encode (state , input ,  &amp ;bits ) ;
/*Copy the bits to an array of char that can be written*/
nbBytes  = speex_bits_write ( &amp ;bits , cbits ,  200 ) ;

/*Write the size of the frame first. This is what sampledec expects but
it's likely to be different in your own application*/

fwrite ( &amp ;nbBytes ,  sizeof ( int ) ,  1 , stdout ) ;
/*Write the compressed data*/
fwrite (cbits ,  1 , nbBytes , stdout ) ;

}

/*Destroy the encoder state*/
speex_encoder_destroy (state ) ;
/*Destroy the bit-packing struct*/
speex_bits_destroy ( &amp ;bits ) ;
fclose (fin ) ;
return  0 ;
}

解压代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
#include
#include

/*The frame size in hardcoded for this sample code but it doesn't have to be*/
#define FRAME_SIZE 160
int main ( int argc ,  char  **argv )
{
char  *outFile ;
FILE  *fout ;
/*Holds the audio that will be written to file (16 bits per sample)*/
short out [FRAME_SIZE ] ;
/*Speex handle samples as float, so we need an array of floats*/
float output [FRAME_SIZE ] ;
char cbits [ 200 ] ;
int nbBytes ;
/*Holds the state of the decoder*/
void  *state ;
/*Holds bits so they can be read and written to by the Speex routines*/
SpeexBits bits ;
int i , tmp ;

/*Create a new decoder state in narrowband mode*/
state  = speex_decoder_init ( &amp ;speex_nb_mode ) ;

/*Set the perceptual enhancement on*/
tmp = 1 ;
speex_decoder_ctl (state , SPEEX_SET_ENH ,  &amp ;tmp ) ;

outFile  = argv [ 1 ] ;
fout  =  fopen (outFile ,  "w" ) ;

/*Initialization of the structure that holds the bits*/
speex_bits_init ( &amp ;bits ) ;
while  ( 1 )
{
/*Read the size encoded by sampleenc, this part will likely be
different in your application*/

fread ( &amp ;nbBytes ,  sizeof ( int ) ,  1 , stdin ) ;
fprintf  (stderr ,  "nbBytes: %dn" , nbBytes ) ;
if  ( feof (stdin ) )
break ;

/*Read the "packet" encoded by sampleenc*/
fread (cbits ,  1 , nbBytes , stdin ) ;
/*Copy the data into the bit-stream struct*/
speex_bits_read_from ( &amp ;bits , cbits , nbBytes ) ;

/*Decode the data*/
speex_decode (state ,  &amp ;bits , output ) ;

/*Copy from float to short (16 bits) for output*/
for  (i = 0 ;i out [i ] =output [i ] ;

/*Write the decoded audio to file*/
fwrite (out ,  sizeof ( short ) , FRAME_SIZE , fout ) ;
}

/*Destroy the decoder state*/
speex_decoder_destroy (state ) ;
/*Destroy the bit-stream truct*/
speex_bits_destroy ( &amp ;bits ) ;
fclose (fout ) ;
return  0 ;
}

通过上面的范例,我们就可以通过JNI的方式包含speex库,并撰写相应的android.mk文件,然后写JNI的encode和decode代码了。

下面的代码摘自android-recoder-6

[code lang="c"]
#include

#include
#include

#include

static int codec_open = 0;

static int dec_frame_size;
static int enc_frame_size;

static SpeexBits ebits, dbits;
void *enc_state;
void *dec_state;

static JavaVM *gJavaVM;

//打开编码函数
extern "C"
JNIEXPORT jint JNICALL Java_com_ryong21_encode_Speex_open
(JNIEnv *env, jobject obj, jint compression) {
int tmp;

if (codec_open++ != 0)
return (jint)0;

speex_bits_init(&ebits);
speex_bits_init(&dbits);

enc_state = speex_encoder_init(&speex_nb_mode);
dec_state = speex_decoder_init(&speex_nb_mode);
tmp = compression;
speex_encoder_ctl(enc_state, SPEEX_SET_QUALITY, &tmp);
speex_encoder_ctl(enc_state, SPEEX_GET_FRAME_SIZE, &enc_frame_size);
speex_decoder_ctl(dec_state, SPEEX_GET_FRAME_SIZE, &dec_frame_size);

return (jint)0;
}
//编码函数实现
extern "C"
JNIEXPORT jint JNICALL Java_com_ryong21_encode_Speex_encode
(JNIEnv *env, jobject obj, jshortArray lin, jint offset, jbyteArray encoded, jint size) {

jshort buffer[enc_frame_size];
jbyte output_buffer[enc_frame_size];
int nsamples = (size-1)/enc_frame_size + 1;
int i, tot_bytes = 0;

if (!codec_open)
return 0;

speex_bits_reset(&ebits);

for (i = 0; i < nsamples; i++) { env->GetShortArrayRegion(lin, offset + i*enc_frame_size, enc_frame_size, buffer);
speex_encode_int(enc_state, buffer, &ebits);
}
//env->GetShortArrayRegion(lin, offset, enc_frame_size, buffer);
//speex_encode_int(enc_state, buffer, &ebits);

tot_bytes = speex_bits_write(&ebits, (char *)output_buffer,
enc_frame_size);
env->SetByteArrayRegion(encoded, 0, tot_bytes,
output_buffer);

return (jint)tot_bytes;
}
//解码函数实现
extern "C"
JNIEXPORT jint JNICALL Java_com_ryong21_encode_Speex_decode
(JNIEnv *env, jobject obj, jbyteArray encoded, jshortArray lin, jint size) {

jbyte buffer[dec_frame_size];
jshort output_buffer[dec_frame_size];
jsize encoded_length = size;

if (!codec_open)
return 0;

env->GetByteArrayRegion(encoded, 0, encoded_length, buffer);
speex_bits_read_from(&dbits, (char *)buffer, encoded_length);
speex_decode_int(dec_state, &dbits, output_buffer);
env->SetShortArrayRegion(lin, 0, dec_frame_size,
output_buffer);

return (jint)dec_frame_size;
}
//获取帧大小
extern "C"
JNIEXPORT jint JNICALL Java_com_ryong21_encode_Speex_getFrameSize
(JNIEnv *env, jobject obj) {

if (!codec_open)
return 0;
return (jint)enc_frame_size;

}
//关闭解码器
extern "C"
JNIEXPORT void JNICALL Java_com_ryong21_encode_Speex_close
(JNIEnv *env, jobject obj) {

if (--codec_open != 0)
return;

speex_bits_destroy(&ebits);
speex_bits_destroy(&dbits);
speex_decoder_destroy(dec_state);
speex_encoder_destroy(enc_state);
}

[/code]

java对jni的导入实现为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class Speex  {

/* quality
* 1 : 4kbps (very noticeable artifacts, usually intelligible)
* 2 : 6kbps (very noticeable artifacts, good intelligibility)
* 4 : 8kbps (noticeable artifacts sometimes)
* 6 : 11kpbs (artifacts usually only noticeable with headphones)
* 8 : 15kbps (artifacts not usually noticeable)
*/

private  static  final  int DEFAULT_COMPRESSION  =  8 ;
private Logger log  = LoggerFactory. getLogger (Speex. class ) ;

Speex ( )  {
}

public  void init ( )  {
load ( ) ;
open (DEFAULT_COMPRESSION ) ;
log. info ( "speex opened" ) ;
}

private  void load ( )  {
try  {
System. loadLibrary ( "speex" ) ;
}  catch  ( Throwable e )  {
e. printStackTrace ( ) ;
}

}

public  native  int open ( int compression ) ;
public  native  int getFrameSize ( ) ;
public  native  int decode ( byte encoded [ ]short lin [ ]int size ) ;
public  native  int encode ( short lin [ ]int offset,  byte encoded [ ]int size ) ;
public  native  void close ( ) ;

}

然后我们就可以在开发代码中使用了。

6 TCP传输

在TCP传输端,可以采取本博客之前的Server端框架,参考 Android多客户端TCP Server框架代码 或者 Android中TCP Server的模型代码

7 接受端还原并播放
播放的方式,我们可以使用AudioTrack或者其他的方式。代码可以参考第四部分。

8 尾声
本博客整体的代码是从android-recorder中学习而来的,一些实现也是他的摘录,算是对音频方面的总结吧。在这里感谢原开源作者。另外对于网上音频聊天的实现,一般都通过服务器中转,如red5,实现方面有的UDP,有的地方TCP。见仁见智了,同时也看具体的业务逻辑而定。
9 代码下载
代码我附上google上面的最新代码,值得阅读。虽然在代码里面他将音频转为flv格式播放,但不影响我们自主封装。
android-recorder-5.1
10 参考文章
1 http://www.eoeandroid.com/thread-101317-1-1.html
2 http://code.google.com/p/android-recorder/
3 http://code.google.com/p/spydroid-ipcamera/
4 https://github.com/pdwryst/SoundSwap/tree/c87e7a2d1c63589f928e653f467c6801bbbb1d20
5 https://github.com/RaabsIn513/RecordAudio
6 https://github.com/trashmaxx/AudioRecorder
7 http://hi.baidu.com/lzhts/blog/item/6773d3ca165b7a49f31fe744.html
8 http://blog.csdn.net/hellogv/article/details/6026455
9 http://www.speex.org/
10 http://www.eoeandroid.com/thread-70014-1-1.html

转自:http://blog.jouhu.com/?p=2442

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
要使用Android的MediaFormat和MediaCodec来录制音频,需要以下步骤: 1. 创建一个MediaFormat对象,设置音频编码格式、采样率、比特率等参数。 2. 创建一个MediaCodec对象,将上一步中创建的MediaFormat对象作为参数传入。 3. 调用MediaCodec的configure方法,将MediaFormat对象作为参数传入。 4. 调用MediaCodec的start方法,开始录制音频。 5. 获取输入缓冲区和输出缓冲区,将音频数据写入输入缓冲区。 6. 从输出缓冲区读取编码后的音频数据。 7. 将编码后的音频数据写入文件或传输网络等。 8. 调用MediaCodec的stop和release方法,停止和释放资源。 以下是一个简单的示例代码: ``` MediaFormat format = MediaFormat.createAudioFormat(MediaFormat.MIMETYPE_AUDIO_AAC, 44100, 1); format.setInteger(MediaFormat.KEY_BIT_RATE, 64000); format.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, 16384); MediaCodec codec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_AUDIO_AAC); codec.configure(format, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE); codec.start(); ByteBuffer[] inputBuffers = codec.getInputBuffers(); ByteBuffer[] outputBuffers = codec.getOutputBuffers(); while (true) { int inputBufferIndex = codec.dequeueInputBuffer(-1); if (inputBufferIndex >= 0) { ByteBuffer inputBuffer = inputBuffers[inputBufferIndex]; // 将音频数据写入输入缓冲区 codec.queueInputBuffer(inputBufferIndex, 0, inputBuffer.limit(), presentationTimeUs, 0); } MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo(); int outputBufferIndex = codec.dequeueOutputBuffer(bufferInfo, 0); if (outputBufferIndex >= 0) { ByteBuffer outputBuffer = outputBuffers[outputBufferIndex]; // 从输出缓冲区读取编码后的音频数据 byte[] encodedData = new byte[bufferInfo.size]; outputBuffer.get(encodedData); // 将编码后的音频数据写入文件或传输网络等 codec.releaseOutputBuffer(outputBufferIndex, false); } } codec.stop(); codec.release(); ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值