FFmpeg用C++多线程解码音频数据
2019-11-11 本文已影响0人
ZebraWei
版权声明:本文为卫伟学习总结文章,转载请注明出处!
上几篇文章详解的介绍了使用MediaRecord和AudioRecord两种对音频进行采集的技术,以及FFmpeg包的编辑和裁剪,生成安卓使用的.so库的技术。本文章将详细介绍使用FFmpeg进行音频解码操作。
一、音频解码过程
音频解码过程如下图所示:
- av_register_all():的作用是初始化所有组件,只有调用了该函数,才能使用复用器和编解码器(源码)
- avformat_open_input()/avformat_close_input(): 函数会读文件头,对 mp4 文件而言,它会解析所有的 box。但它知识把读到的结果保存在对应的数据结构下。这个时候,AVStream 中的很多字段都是空白的。
- av_dump_format(): 打印视音频信息
- avformat_find_stream_info():读取一部分视音频数据并且获得一些相关的信息,会检测一些重要字段,如果是空白的,就设法填充它们。因为我们解析文件头的时候,已经掌握了大量的信息,avformat_find_stream_info 就是通过这些信息来填充自己的成员,当重要的成员都填充完毕后,该函数就返回了。这中情况下,该函数效率很高。但对于某些文件,单纯的从文件头中获取信息是不够的,比如 video 的 pix_fmt 是需要调用 h264_decode_frame 才可以获取其pix_fmt的。
- av_find_best_stream(): 获取音视频及字幕的 stream_index , 以前没有这个函数时,我们一般都是写的 for 循环。
- av_packet_free(): 首先将 AVPacket 指向的数据域的引用技术减1(数据域的引用技术减为0时会自动释放) 接着,释放为 AVPacket 分配的空间。
- av_packet_unref(): 减少数据域的引用技术,当引用技术减为0时,会自动释放数据域所占用的空间。
二、获取音频Meta信息
对于amr格式的音频文件而言,如何去获取它的采样率以及是否单声道。如果用ffmpeg那很easy就能解决。但问题是不会,那么我们就只能用其他的第三方库。当然如果你了解其原理,甚至可以自己分析二进制文件。
int audio_stream_idx;
AVStream *audio_stream;
av_register_all();
avformat_network_init();
pFormatCtx = avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, url, NULL, NULL) != 0)
{
if(LOG_DEBUG)
{
LOGE("can not open url :%s", url);
}
return;
}
if(avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
if(LOG_DEBUG)
{
LOGE("can not find streams from %s", url);
}
return;
}
// 获取采样率和通道
audio_stream_idx = av_find_best_stream(pFormatCtx, AVMediaType::AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
audio_stream = pFormatCtx->streams[audio_stream_idx];
LOGE("采样率:%d, 通道数: %d", audio_stream->codecpar->sample_rate, audio_stream->codecpar->channels);
二、解码音频数据
关于解码函数 avcodec_decode_audio4 已经过时了,取而代之的是 avcodec_send_packet 和 avcodec_receive_frame 。
void WlFFmpeg::start() {
if(audio == NULL)
{
if(LOG_DEBUG)
{
LOGE("audio is null");
return;
}
}
int count = 0;
while(1)
{
AVPacket *avPacket = av_packet_alloc();
if(av_read_frame(pFormatCtx, avPacket) == 0)
{
if(avPacket->stream_index == audio->streamIndex) //音频帧数据
{
//解码操作
count++;
if(LOG_DEBUG)
{
LOGE("解码第 %d 帧", count);
}
av_packet_free(&avPacket);
av_free(avPacket);
} else{
av_packet_free(&avPacket);
av_free(avPacket);
}
} else{
if(LOG_DEBUG)
{
LOGE("decode finished");
}
av_packet_free(&avPacket);
av_free(avPacket);
break;
}
}
三、子线程下进行解码操作
WlFFmpeg::WlFFmpeg(WlCallJava *callJava, const char *url) {
this->callJava = callJava;
this->url = url;
}
void *decodeFFmpeg(void *data)
{
WlFFmpeg *wlFFmpeg = (WlFFmpeg *) data;
wlFFmpeg->decodeFFmpegThread();
pthread_exit(&wlFFmpeg->decodeThread);
}
void WlFFmpeg::parpared() {
pthread_create(&decodeThread, NULL, decodeFFmpeg, this);
}
[代码](链接: https://pan.baidu.com/s/1RrqmBf2adXygilH5PEMnaQ)
密码:v8ef