1. C++多线程解码音频数据
之前总结过一篇 FFMpeg 解码流程的博客FFMpeg 解码流程
接下来,对照着上面的流程,使用代码来实现 FFmpeg 的解码流程。在文末有源码下载。
1.1 解码流程
1.1.1 开启线程
- 调用 prepared() 方法,开启线程。
- 在 callbackDecode 中执行
decodeFFmpegThread
方法。
extern "C"
JNIEXPORT void JNICALL
Java_com_example_audioplayer_player_AudioPlayer__1prepare(JNIEnv *env, jobject instance,
jstring source_) {
const char *source = env->GetStringUTFChars(source_, 0);
if (ffmpeg == NULL) {
if (callJava == NULL) {
callJava = new CallJava(env, jvm, &instance);
}
//自己定义的一个类,用于解码音频数据
ffmpeg = new FFmpeg(callJava, source);
//1.调用准备方法
ffmpeg->prepare();
}
}
//2.准备方法
void FFmpeg::prepare() {
pthread_create(&decodeThread, NULL, callbackDecode, this);
}
//构造方法
FFmpeg::FFmpeg(CallJava *callJava, const char *url) {
this->callJava = callJava;
this->url = url;
}
//3.线程执行体
void *callbackDecode(void *data) {
FFmpeg *ffmpeg = (FFmpeg *) data;
ffmpeg->decodeFFmpegThread();
pthread_exit(&ffmpeg->decodeThread);
}
接下来,解码流程会在 decodeFFmpegThread
方法中执行。
1.1.2 准备阶段
下面是 decodeFFmpegThread
方法的内容:
- 注册
//注册
av_register_all();
avformat_network_init();
- 打开文件或网络流
avFormatContext = avformat_alloc_context();
if (avformat_open_input(&avFormatContext, url, NULL, NULL) != 0) {
LOGE("avformat_open_input failed...");
return;
}
- 获取流信息
if (avformat_find_stream_info(avFormatContext, NULL) < 0) {
LOGE("avformat_find_stream_info failed...");
return;
}
- 获取音频流
这里只解码音频,因此只需要找到 codec_type
为 AVMEDIA_TYPE_AUDIO
流信息即可。
for (int i = 0; i < avFormatContext->nb_streams; i++) {
//找到对应的音频流信息
if (avFormatContext->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
if (audioInfo == NULL) {
//创建 AudioInfo 保存音频相关信息
audioInfo = new AudioInfo();
audioInfo->streamIndex = i;
audioInfo->avCodecParameters = avFormatContext->streams[i]->codecpar;
break;
}
}
}
- 根据 AVCodecID 获取解码器
const AVCodec *avCodec = avcodec_find_decoder(audioInfo->avCodecParameters->codec_id);
if (!avCodec) {
LOGE("avcodec_find_decoder failed...");
return;
}
- 利用解码器创建解码器上下文
audioInfo->avCodecContext = avcodec_alloc_context3(avCodec);
if (!audioInfo->avCodecContext) {
LOGE("avcodec_alloc_context3 failed...");
return;
}
if (avcodec_parameters_to_context(audioInfo->avCodecContext, audioInfo->avCodecParameters) <
0) {
LOGE("avcodec_parameters_to_context failed...");
return;
}
- 打开解码器
至此,打开解码器之后,音频准备工作已经完成,接下来就可以解析每一个 AvPacket
数据了
if (avcodec_open2(audioInfo->avCodecContext, avCodec, 0) != 0) {
LOGE("avcodec_open2 failed...");
return;
}
1.1.3 解码 AvPacket 阶段
解码 AvPacket
阶段就是解码每一帧音频数据,AvPacket
存放了每一帧的音频数据。
AVPacket *avPacket = av_packet_alloc();
av_read_frame(avFormatContext, avPacket)
下面这个写一个 start()
函数,负责解码音频数据。
void FFmpeg::start() {
//判断
if (audioInfo == NULL) {
LOGE("start failed audio info is null.")
return;
}
int count = 0;
//死循环判断
while (1) {
AVPacket *avPacket = av_packet_alloc();
if (av_read_frame(avFormatContext, avPacket) == 0) {
if (avPacket->stream_index == audioInfo->streamIndex) {
count++;
LOGD("当前解码第%d帧", count);
av_packet_free(&avPacket);
av_free(avPacket);
} else {
av_packet_free(&avPacket);
av_free(avPacket);
}
} else {
LOGD("解码完成,总共解码%d帧", count);
av_packet_free(&avPacket);
av_free(avPacket);
break;
}
}
}
1.2 源码
下载源码,运行Demo ,点击准备
按钮,即可在控制台中看到解码的数据
11-25 22:45:52.752 27636-27868/example.com.jniexample I/MainActivity: onPrepared
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第1帧
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第2帧
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第3帧
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第4帧
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第5帧
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第6帧
11-25 22:45:52.754 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第7帧
...
11-25 22:45:57.530 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第8403帧
11-25 22:45:57.530 27636-27870/example.com.jniexample D/liaoweijian: 当前解码第8404帧
11-25 22:45:57.530 27636-27870/example.com.jniexample D/liaoweijian: 解码完成,总共解码8404帧
记录于 2018年11月25日