一、思路:
在Jni层初始化好OpenSl,解码线程不停地从packet queue中获取packet(这个部分请参考上一篇文章),并解码(与视频解码类似,解码后的数据都是封装在AVFrame对象中),解码好的frame对象放到frame queue中;OpenSl启动后,会不停地从frame queue中获取frame对象,转化为PCM格式后,放到OpenSl的播放queue中即可正常播放。
二、上代码:
Jni层:
/**
* 初始化opensl
*/
void AudioChannel::initOpenSl() {
// 1.创建音频引擎 -- 2.设置混音器 -- 3.创建播放器(录音器) -- 4.设置缓冲队列和回调函数 -- 5.设置播放状态 -- 6.启动回调函数
// 音频引擎
SLEngineItf engineInteface = NULL;
// 音频对象
engineObject = NULL;
// 混音器
outputMixObject = NULL;
// 播放器
playerObject = NULL;
// 回调接口
playInteface = NULL;
// 缓冲队列
SLAndroidSimpleBufferQueueItf playerBufferQueue = NULL;
// ========= 1.初始化播放引擎 =========
SLresult result;
result = slCreateEngine(&engineObject, 0, NULL, 0, NULL, NULL);
if (result != SL_RESULT_SUCCESS) {
return;
}
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
return;
}
// 获取到音频接口,相当于surfaceHolder
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInteface);
if (result != SL_RESULT_SUCCESS) {
return;
}
// ========= 2.初始化混音引擎 =========
result = (*engineInteface)->CreateOutputMix(engineInteface, &outputMixObject, 0, 0, 0);
if (result != SL_RESULT_SUCCESS) {
return;
}
// 初始化混音器outputMixObject
result = (*outputMixObject)->Realize(outputMixObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
return;
}
LOGD("channel = %d, sampleRate = %d, format = %d", decoderContext->channels,
decoderContext->sample_rate, decoderContext->sample_fmt);
SLuint32 channelMask = SL_SPEAKER_FRONT_CENTER;
// 双声道
if (decoderContext->channels == 2) {
channelMask = SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT; // 立体声 (左前右后)
}
// configure audio source
SLDataLocator_AndroidSimpleBufferQueue loc_bufq = {SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
2}; // 2个声道
SLDataFormat_PCM format_pcm = {SL_DATAFORMAT_PCM, // 播放的是PCM格式
decoderContext->channels, // 2个声道
(decoderContext->sample_rate *
1000), // sample_rate * 1000才是真正的采样率(可以把sample_rate打印出来,发现是441?) 因为音频的播放速度是由采样率决定的,所以音频的播放速率无需干预
SL_PCMSAMPLEFORMAT_FIXED_16, // 位数 16位
SL_PCMSAMPLEFORMAT_FIXED_16, // 和位数一致就可以
channelMask, //
SL_BYTEORDER_LITTLEENDIAN}; // 小端模式
SLDataSource audioSrc = {&loc_bufq, &format_pcm};
// configure audio sink
SLDataLocator_OutputMix loc_outmix = {SL_DATALOCATOR_OUTPUTMIX, outputMixObject};
SLDataSink audioSnk = {&loc_outmix, NULL};
const SLInterfaceID ids[1] = {SL_IID_BUFFERQUEUE};
const SLboolean req[1] = {SL_BOOLEAN_TRUE};
result = (*engineInteface)->CreateAudioPlayer(engineInteface,
&playerObject, // 播放器
&audioSrc, // 播放器参数 缓冲队列, 播放格式
&audioSnk, // 播放缓冲区
1, // 播放接口回调个数
ids, // 设置播放器队列ID
req // 是否采用内置的播放队列
);
if (result != SL_RESULT_SUCCESS) {
return;
}
result = (*playerObject)->Realize(playerObject, SL_BOOLEAN_FALSE);
if (result != SL_RESULT_SUCCESS) {
return;
}
// get the play interface
// 获取播放器接口
result = (*playerObject)->GetInterface(playerObject, SL_IID_PLAY, &playInteface);
if (result != SL_RESULT_SUCCESS) {
return;
}
// get the buffer queue interface
result = (*playerObject)->GetInterface(playerObject, SL_IID_BUFFERQUEUE,
&playerBufferQueue);
// get the buffer queue interface
if (result != SL_RESULT_SUCCESS) {
return;
}
// 注册回调
// register callback on the buffer queue
result = (*playerBufferQueue)->RegisterCallback(playerBufferQueue, bqPlayerCallback, this);
if (result != SL_RESULT_SUCCESS) {
return;
}
// set the player's state to playing
// 设置播放状态 --- 播放/暂停/停止 状态
result = (*playInteface)->SetPlayState(playInteface, SL_PLAYSTATE_PLAYING);
if (result != SL_RESULT_SUCCESS) {
return;
}
// 相当于手动调用播放按钮
bqPlayerCallback(playerBufferQueue, this);
}
/**
* this callback handler is called every time a buffer finishes playing
* 会自动不断地回调
* @param bq
* @param context
*/
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context) {
AudioChannel *audioChannel = static_cast<AudioChannel *>(context);
// pcm 原始音频数据
// 数据存放在outBuffer里,返回值为outBuffer的有效长度, 音频的一个package里,有可能会有多帧数据?
int dataLength = audioChannel->getPcm();
if (audioChannel->isPlaying) {
if (dataLength > 0) {
// 压入队列里播放, 入参是buffer 和 数据的有效长度
(*bq)->Enqueue(bq, audioChannel->out_buffer, dataLength);
}
} else {
LOGD("audio channel callback stop.");
}
}
/**
* 获取PCM数据
* @return PCM数据的有效长度
*/
int AudioChannel::getPcm() {
AVFrame *frame = 0;
int ret = 0;
int out_buffer_size = 0;
// int out_buffer_size2 = 0;
while (isPlaying) {
ret = frame_queue.get(frame);
if (!isPlaying) {
break;
}
if (!ret) {
continue;
}
// 转换参数
uint64_t dst_nb_samples = av_rescale_rnd(
swr_get_delay(swrContext, frame->sample_rate) + frame->nb_samples,
outputSampleRate,
frame->sample_rate,
AV_ROUND_UP
);
// LOGD("dst_nb_samples = %lld, frame->nb_samples = %d", dst_nb_samples,
// frame->nb_samples); // 2 * 44100
// 转换 成 pcm,返回值为转换后的sample个数
// out_buffer取址,传入,会在ffmpeg中进行初始化
int nb = swr_convert(swrContext, &out_buffer, dst_nb_samples,
(const uint8_t **) (frame->data), frame->nb_samples);
int out_channnel_nb = av_get_channel_layout_nb_channels(outputChannel);
// buffer的大小是固定的,但是frame->data的大小不一定就等于buffer大小,所以要做一下处理
// buffer的真实大小
out_buffer_size = av_samples_get_buffer_size(NULL, out_channnel_nb, nb,
outputSamplaFormat, 1);
// 设置当前音频播放的时间戳(音视频同步时使用) --- pts 显示时间戳; dts 解码时间戳 (B帧时,两都会不一样)
colock = frame->pts * av_q2d(timeBase);
break;
}
// 转换成功后,释放frame对象
releaseFrame(frame);
return out_buffer_size;
}
网友评论