Android FFmpeg视频播放器三 音频封装格式解码播放
Posted 若之灵动
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android FFmpeg视频播放器三 音频封装格式解码播放相关的知识,希望对你有一定的参考价值。
Android Android FFmpeg视频播放器二 视频封装格式解码播放
视频解封装之后就会得到音频流和视频流,解封状得到的数据是AVPackage类型数据,需要进一步解码成AVFrame一帧一帧数据才能进行播放。
1.从AVPackage队列获取数据进行解码操作
pthread_create(&pid_audio_decode, nullptr, task_audio_decode, this);
/**
* 音频解码线程回调
* @param args
* @return
*/
void *task_audio_decode(void * args)
auto *audio_channel = static_cast<AudioChannel *>(args);
audio_channel->audio_decode();
return nullptr;
/**
* 音频:取出队列的压缩包 进行解码 解码后的原始包 再push队列中去 (音频:PCM数据)
*/
void AudioChannel::audio_decode()
AVPacket *pkt = nullptr;
while (isPlaying)
if (isPlaying && frames.size() > AV_MAX_SIZE)
av_usleep(10000);
continue;
/*阻塞式函数*/
int ret = packets.getQueueAndDel(pkt);
if (!isPlaying)
/*如果关闭了播放,跳出循环,releaseAVPacket(&pkt);*/
break;
if (!ret)
/*压缩包加入队列慢,继续*/
continue;
/*
* 1.发送pkt(压缩包)给缓冲区,
* 2.从缓冲区拿出来(原始包)
* */
ret = avcodec_send_packet(codecContext, pkt);
if (ret)
/* avcodec_send_packet 出现了错误,结束循环*/
break;
/*AVFrame: 解码后的视频原始数据包 pcm */
AVFrame *frame = av_frame_alloc();
/*从 FFmpeg缓冲区 获取 原始包*/
ret = avcodec_receive_frame(codecContext, frame);
if (ret == AVERROR(EAGAIN))
/*有可能音频帧也会获取失败,重新获取一次*/
continue; //
else if (ret != 0)
if (frame)
releaseAVFrame(&frame);
break;
/*原始包-- PCM数据 将PCM数据添加到帧队列*/
frames.insertToQueue(frame);
av_packet_unref(pkt);
releaseAVPacket(&pkt);
/*释放结构体内部成员在堆区分配的内存*/
av_packet_unref(pkt);
/*释放AVPacket **/
releaseAVPacket(&pkt);
- pthread_create:AVPackage解码得到AVFrame耗时操作,创建线程
- getQueueAndDel:从AVPackage队列获取数据,阻塞队列,如果队列为空会进行wait阻塞
- avcodec_send_packet:将获取到的AVPackage数据发送给ffmpeg缓冲区,ffmpeg会进行解码操作。
- avcodec_receive_frame:从ffmpeg缓冲区得到解码之后的pcm数据。
- frames.insertToQueue(frame):将pcm数据添加到帧队列中。
2.初始化OpenSL ES
/*第二线线程:视频:从队列取出原始包,播放 音频播放OpenSLES*/
pthread_create(&pid_audio_play, nullptr, task_audio_play, this);
/**
* 音频播放子线程回调
* @param args
* @return
*/
void *task_audio_play(void *args)
auto *audio_channel = static_cast<AudioChannel *>(args);
audio_channel->audio_play();
return nullptr;
/**
* 音频:从队列取出原始包PCM,OpenSLES音频播放
*/
void AudioChannel::audio_play()
/*用于接收 执行成功或者失败的返回值*/
SLresult result;
/*创建引擎对象并获取 创建引擎对象:SLObjectItf engineObject*/
result = slCreateEngine(&engineObject, 0, 0, 0, 0, 0);
if (SL_RESULT_SUCCESS != result)
LOGE("创建引擎 slCreateEngine error");
return;
/*
* 初始化引擎
* SL_BOOLEAN_FALSE:同步等待创建成功
* */
result = (*engineObject)->Realize(engineObject, SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result)
LOGE("创建引擎 Realize error");
return;
/*
* 获取引擎接口
* */
result = (*engineObject)->GetInterface(engineObject, SL_IID_ENGINE, &engineInterface);
if (SL_RESULT_SUCCESS != result)
LOGE("创建引擎接口 Realize error");
return;
/*健壮判断*/
if (engineInterface)
LOGD("创建引擎接口 create success");
else
LOGD("创建引擎接口 create error");
return;
/*创建混音器 环境特效,混响特效 */
result = (*engineInterface)->CreateOutputMix(engineInterface, &outputMixObject,
0, nullptr, nullptr);
if (SL_RESULT_SUCCESS != result)
LOGD("初始化混音器 CreateOutputMix failed");
return;
/*
* 初始化混音器
* SL_BOOLEAN_FALSE:同步等待创建成功
* */
result = (*outputMixObject)->Realize(outputMixObject,
SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result)
LOGD("初始化混音器 (*outputMixObject)->Realize failed");
return;
/*创建buffer缓存类型的队列 */
SLDataLocator_androidSimpleBufferQueue loc_bufq =
SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,
10;
/*
* 声明PCM 数据参数集 format_pcm
* pcm数据格式不能直接播放,需要设置PCM的参数集
* SL_DATAFORMAT_PCM:数据格式为pcm格式
* 2:双声道
* SL_SAMPLINGRATE_44_1:采样率为44100
* SL_PCMSAMPLEFORMAT_FIXED_16:采样格式为16bit
* SL_PCMSAMPLEFORMAT_FIXED_16:数据大小为16bit
* SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT:左右声道(双声道)
* SL_BYTEORDER_LITTLEENDIAN:小端模式 字节序(小端) 例如:int类型四个字节(高位在前 还是 低位在前 的排序方式,一般都是小端)
* */
SLDataFormat_PCM format_pcm = SL_DATAFORMAT_PCM,
2,
SL_SAMPLINGRATE_44_1,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_PCMSAMPLEFORMAT_FIXED_16,
SL_SPEAKER_FRONT_LEFT | SL_SPEAKER_FRONT_RIGHT,
SL_BYTEORDER_LITTLEENDIAN;
/**
* 使用数据源 和PCM数据格式,初始化SLDataSource
* 独立声卡:24bit 集成声卡16bit
*/
SLDataSource audiosrc = &loc_bufq, &format_pcm;
/*
* 配置音轨(输出)
* 设置混音器
* SL_DATALOCATOR_OUTPUTMIX:输出混音器类型
* */
SLDataLocator_OutputMix loc_outmix = SL_DATALOCATOR_OUTPUTMIX,
outputMixObject;
/*得到outmix最终混音器*/
SLDataSink audioSnk = &loc_outmix, NULL;
/* 需要的接口 操作队列的接口*/
const SLInterfaceID ids[1] = SL_IID_BUFFERQUEUE;
const SLboolean req[1] = SL_BOOLEAN_TRUE;
/*
* 创建播放器 SLObjectItf bqPlayerObject
* 参数1:引擎接口
* 参数2:播放器
* 参数3:音频配置信息
* 参数4:混音器
* 参数5:开放的参数的个数
* 参数6:代表我们需要 Buff
* 参数7:代表我们上面的Buff 需要开放出去
* */
result = (*engineInterface)->CreateAudioPlayer(engineInterface,
&bqPlayerObject,
&audioSrc,
&audioSnk,
1,
ids,
req
);
if (SL_RESULT_SUCCESS != result)
LOGD("创建播放器 CreateAudioPlayer failed!");
return;
/*
* 初始化播放器:SLObjectItf bqPlayerObject
* SL_BOOLEAN_FALSE:同步等待创建成功
* */
result = (*bqPlayerObject)->Realize(bqPlayerObject,
SL_BOOLEAN_FALSE);
if (SL_RESULT_SUCCESS != result)
LOGD("实例化播放器 CreateAudioPlayer failed!");
return;
LOGD("创建播放器 CreateAudioPlayer success!");
/*
* 获取播放器接口 播放全部使用播放器接口
* SL_IID_PLAY:播放接口 == iplayer
* */
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_PLAY,
&bqPlayerPlay);
if (SL_RESULT_SUCCESS != result)
LOGD("获取播放接口 GetInterface SL_IID_PLAY failed!");
return;
LOGI("创建播放器 Success");
/*
* 设置回调函数
* 获取播放器队列接口:SLAndroidSimpleBufferQueueItf bqPlayerBufferQueue
* 播放需要的队列
* */
result = (*bqPlayerObject)->GetInterface(bqPlayerObject, SL_IID_BUFFERQUEUE,
&bqPlayerBufferQueue);
if (result != SL_RESULT_SUCCESS)
LOGD("获取播放队列 GetInterface SL_IID_BUFFERQUEUE failed!");
return;
/*
* 设置回调 void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *context)
* 传入刚刚设置好的队列
* 给回调函数的参数
* */
(*bqPlayerBufferQueue)->RegisterCallback(bqPlayerBufferQueue,
bqPlayerCallback,
this);
LOGI("设置播放回调函数 Success");
/*设置播放器状态为播放状态*/
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_PLAYING);
LOGI("设置播放器状态为播放状态 Success");
/*手动激活回调函数*/
bqPlayerCallback(bqPlayerBufferQueue, this);
LOGI("手动激活回调函数 Success");
- pthread_create:创建音频播放线程。
- slCreateEngine:创建OpenSL ES 引擎对象:SLObjectItf engineObject。
- Realize(engineObject, SL_BOOLEAN_FALSE):同步的方式初始化引擎对象engineObject
- (*engineObject)->GetInterface:通过引擎对象获取引擎接口。
- (*engineInterface)->CreateOutputMix:通过引擎接口创建混音器对象outputMixObject
- Realize(outputMixObject,SL_BOOLEAN_FALSE):同步初始化混合音器对象
- SLDataLocator_AndroidSimpleBufferQueue loc_bufq = SL_DATALOCATOR_ANDROIDSIMPLEBUFFERQUEUE,10:创建buffer缓存类型的队列
- SLDataFormat_PCM format_pcm:设置PCM数据格式,通道数量、采样率、位深、大小端。
- SLDataSource audioSrc = &loc_bufq, &format_pcm:前面的缓存队列和数据PCM格式是为了初始化SLDataSource
- CreateAudioPlayer:创建播放器 SLObjectItf bqPlayerObject
- Realize(bqPlayerObject, SL_BOOLEAN_FALSE):实例化音频播放器
- (*bqPlayerObject)->GetInterface:创建播放器接口,播放全部使用播放器接口bqPlayerPlay
- (*bqPlayerObject)->GetInterface:得到播放器接口队列bqPlayerBufferQueue
- RegisterCallback:注册接口播放器队列回调,bqPlayerCallback这个是一个函数指针是一个回调函数。
- bqPlayerCallback(bqPlayerBufferQueue, this):执行函数bqPlayerCallback,将接口队列当作参数进行传递。
3.OpenSL ES播放PCM格式的音频数据
/**
* 回调函数
* @param bq SLAndroidSimpleBufferQueueItf队列
* @param args this 给回调函数的参数
*/
void bqPlayerCallback(SLAndroidSimpleBufferQueueItf bq, void *args)
auto *audio_channel = static_cast<AudioChannel *>(args);
int pcm_size = audio_channel->getPCM();
/*
* PCM数据添加数据到缓冲区里面去,声音就播放出来了
* bq:队列接口本身,因为没有this,所以把自己传进去了
* audio_channel->out_buffers:音频PCM数据
* pcm_size:PCM数据大小
* */
(*bq)->Enqueue(
bq,
audio_channel->out_buffers,
pcm_size);
/**
* 输入的音频数据的采样率可能是各种各样的,为了兼容所以需要重采样
* @return 重采样之后音频数据的大小
*/
int AudioChannel::getPCM()
int pcm_data_size = 0;
/*
* PCM数据在队列frames队列中:frame->data == PCM数据
* */
AVFrame *frame = 0;
while (isPlaying)
int ret = frames.getQueueAndDel(frame);
if (!isPlaying)
break;
if (!ret)
/*原始包加入队列慢,再次获取一下*/
continue;
/*
* 开始重采样
* 数据源10个48000 目标:44100 11个44100
* 第一个参数:获取下一个输入样本相对于下一个输出样本将经历的延迟
* 第二个参数: 输出采样率
* 第三个参数:输入采样率
* 第四个参数:先上取
* */
int dst_nb_samples = av_rescale_rnd(swr_get_delay(swr_ctx, frame->sample_rate) +frame->nb_samples,
out_sample_rate,
frame->sample_rate,
AV_ROUND_UP);
/*
* pcm的处理逻辑
* 音频播放器的数据格式是在下面定义的
* 而原始数据(待播放的音频pcm数据)
* 重采样工作
* 返回的结果:每个通道输出的样本数(注意:是转换后的) 做一个简单的重采样实验(通道基本上都是:1024)
* */
int samples_per_channel = swr_convert(swr_ctx,
/*输出区域*/
/*重采样后的buffer数据*/
&out_buffers,
/*单通道的样本数 无法与out_buffers对应,需要pcm_data_size从新计算*/
dst_nb_samples,
/*输入区域*/
/*队列的AVFrame *PCM数据 未重采样的*/
(const uint8_t **) frame->data,
/*输入的样本数*/
frame->nb_samples);
/*由于out_buffers 和 dst_nb_samples 无法对应,所以需要重新计算
* 计算公式:样本数量*位深(16位2个字节)*通道数量
* 比如:941通道样本数 * 2样本格式字节数 * 2声道数 =3764
* */
pcm_data_size = samples_per_channel * out_sample_size *
out_channels;
break;
/*
* FFmpeg录制麦克风 输出 每一个音频包的size == 4096
* 4096是单声道的样本数, 44100是每秒钟采样的数
* 样本数 = 采样率 * 声道数 * 位声
* 采样率 44100是每秒钟采样的次数
* */
av_frame_unref(frame);
av_frame_free(&frame);
return pcm_data_size;
- 音频播放,初始化OpenSL ES之后,只需要通过音频缓冲队列的Enqueue方法将数据添加到队列就可以播放音频了
- getPCM():从音频AVFrame帧队列,获取已经将解码的PCM数据,获取之后不能直接使用,因为音频的采样率不确定,为了兼容性需要进行重采样,然后重新计算重采样之后的数据大小。
- swr_convert:对数据进行重采样,dst_nb_samples这个是单通道的样本数,out_buffers这个是重采样之后PCM数据大小。
- PCM数据包大小计算:单通道样本数*通道数*位深
4.音频播放结束之后OpenSL ES资源释放
void AudioChannel::stop()
/*设置停止状态*/
if (bqPlayerPlay)
(*bqPlayerPlay)->SetPlayState(bqPlayerPlay, SL_PLAYSTATE_STOPPED);
bqPlayerPlay = nullptr;
/*销毁播放器*/
if (bqPlayerObject)
(*bqPlayerObject)->Destroy(bqPlayerObject);
bqPlayerObject = nullptr;
bqPlayerBufferQueue = nullptr;
/*销毁混音器*/
if (outputMixObject)
(*outputMixObject)->Destroy(outputMixObject);
outputMixObject = nullptr;
/*销毁引擎*/
if (engineObject)
(*engineObject)->Destroy(engineObject);
engineObject = nullptr;
engineInterface = nullptr;
总结:音频播放流程:将AVPackage解码成AVFrame数据->将AVFrame帧数据放入队列->对OpenSL ES进行初始化,得到缓存队列接口->为了兼容性对数据进行重采样->将重采样的数据加入到缓冲接口队列中去因为就播放出来了。
Android使用FFmpeg播放视频(二):音频播放
参考技术A Android使用FFmpeg播放视频(一):视频播放Android NDK开发:利用OpenSL ES实现声音播放
这里我创建了两个JNI函数,一个是播放的,一个是释放的如下:
这里我在用于播放的JNI函数中依次初始化了FFmpeg和OpenSLES
其中初始化FFmpeg的函数中的逻辑其实和使用FFmpeg播放视频画面中的逻辑差不多,主要区别就是要找到音频的索引以及后面对于解析音频的一些配置;而初始化OpenSLES基本就和之前使用OpenSLES播放PCM数据是一样的,具体如下:
最后再加入释放资源的逻辑即可
这里的案例源码是和之前播放视频画面的分开了
https://gitee.com/itfitness/ffmpeg-audio-demo
以上是关于Android FFmpeg视频播放器三 音频封装格式解码播放的主要内容,如果未能解决你的问题,请参考以下文章