上篇文章我们利用FFmpeg+ANativeWindwo实现了视频的解码和渲染,已经完成视频画面在SurfaceView上显示。还没阅读上一篇文章的同学建议先阅读:Android FFmpeg开发(二),实现视频解码和渲染
本文我们将对音频流进行解码和渲染,这样就能实现一个较完整的视频播放器的效果。具体技术选型如下:
使用FFmpeg解码音频流
使用OpenSL ES播放音频PCM数据
一、FFmpeg解码音频流
音频流程和视频解码流程整体类似,具体流程大家可以看上一篇文章。本文说一下音频解码相关的特有细节。
首先,是格式转换。上一篇我们也对视频解码做了格式转换(YUV->RGBA)。同样的,音频解码我们也需要按照我们预期的格式进行格式转换,具体如下所示:
AVCodecContext *codecContext = getCodecContext();
mSwrContext = swr_alloc();
av_opt_set_int(mSwrContext, "in_channel_layout", codecContext->channel_layout, 0);
av_opt_set_int(mSwrContext, "out_channel_layout", AV_CH_LAYOUT_STEREO, 0);
av_opt_set_int(mSwrContext, "in_sample_rate", codecContext->sample_rate, 0);
av_opt_set_int(mSwrContext, "out_sample_rate", 44100, 0);
av_opt_set_int(mSwrContext, "in_sample_fmt", codecContext->sample_fmt, 0);
av_opt_set_int(mSwrContext, "out_sample_fmt", AV_SAMPLE_FMT_S16, 0);
swr_init(mSwrContext);
// resample
mNbSample = av_rescale_rnd(ACC_NB_SAMPLES, AUDIO_DST_SAMPLE_RATE,
codecContext->sample_rate,AV_ROUND_UP);
mDstFrameDataSize = av_samples_get_buffer_size(NULL, AUDIO_DST_CHANNEL_COUNTS,
mNbSample, DST_SAMPLE_FORMAT, 1);
// 分配OpenSL播放音频的帧内存
mAudioOutBuffer = (uint8_t *) malloc(mDstFrameDataSize);
我们需要把原始的音频格式(包括声道、采样率、采样格式)和目标音频的格式(包括声道、采样率、采样格式)传递给av_opt_set_int方法,然后调用swr_init初始化SwrContext上下文出来。最后,调用swr_convert,传入SwrContext即可获得目标格式的音频。如下所示:
void AudioDecoder::onFrameAvailable(AVFrame *frame) {
LOGD("AudioDecoder::onFrameAvailable frame=%p, frame->nb_samples=%d\n", frame, frame->nb_samples);
if (mAudioRender) {
// 将解码出来音频帧进行重采样,采样后数据存入mAudioOutBuffer中
int result = swr_convert(mSwrContext, &mAudioOutBuffer,mDstFrameDataSize