Android 利用 FFmpeg 解码音视频数据

Android 利用 FFmpeg 解码音视频数据

FFMpeg解码流程图解

Android 利用 FFmpeg 解码音视频数据

FFMpeg解码代码流程

以下的音频解码的代码实现流程对应于上面的解码图解流程(视频的解码流程跟音频类似)

Android 利用 FFmpeg 解码音视频数据

FFMpeg的处理流程

结合上面的流程得到以下的流程:

得到输入文件 -> 解封格式 -> 得到编码的数据包 -> 解码数据包 -> 得到解码后的数据帧 -> 处理数据帧 -> 编码 -> 得到编码后的数据包 -> 封装格式 -> 输出文件

一、本节目标

继上节获取解封装的 AvPacket 数据包之后,我们知道 AvPacket 存储的都是编码后的数据,因此我们需要将数据包进行解码,从而得到原始的数据,而 FFmpeg 使用 AvFrame 这个数据结构来存储解码后的数据。

对于解码后的数据:

视频原始数据一般是用 yuv 表示。 音频原始数据一般用 pcm 表示。

而在开始之前,我们还是来回顾一下 FFmpeg 处理流的整个过程。

FFmeg 处理流程如下:

1、得到输入流,打开输入流 2、解封装格式->得到编码数据包 AvPacket 3、解码数据包->得到解码的原始数据 AvFrame 4、处理数据->例如滤镜处理,重采样,像素格式转化等 5、编码原始数据->得到编码后的数据 6、封装格式 7、得到输出文件

根据本节目标,我们可以知道,我们重点要了解的就是第 3 步,解码数据包得到 AvFrame 数据。

二、解码音视频的步骤

2.1、 获取解码器

0、注册编解码器 avcodec_register_all(); 1、 获取解码器 AVCodec

因为音频和视频的解码器 AVCodec 是不一样的,而在 FFmpeg 中每一个解码器都会对应的一个codec_id,我们可以通过这个 id 就可以获取对应的解码器了。当前除了通过 codec_id 获取之外,也可以通过 name 来获取,目前先不考虑这种方式。

下面来看一下如何获取:

//得到视音频解码器 AVCodec *audioCodec = avcodec_find_decoder( avFormatContext->streams[audioIndex]->codecpar->codec_id); //得到视频解码器 AVCodec *vedioCodec = avcodec_find_decoder( avFormatContext->streams[videoIndex]->codecpar->codec_id); 2、分配解码器上下文空间 AVCodecContext

创建 AVCodecContext 空间

AVCodecContext *ac = avcodec_alloc_context3(audioCodec); AVCodecContext *vc = avcodec_alloc_context3(vedioCodec); 3、初始化解码器上下文

将 AVCodecParameters 的参数赋值给 AVCodecContext。

ret = avcodec_parameters_to_context(ac, avFormatContext->streams[audioIndex]->codecpar); if (ret < 0) { LOGE(“avcodec_parameters_to_context audio failed…”) return; } ret = avcodec_parameters_to_context(vc, avFormatContext->streams[videoIndex]->codecpar); if (ret < 0) { LOGE(“avcodec_parameters_to_context vedio failed…”) return; } 4、打开解码器

使用 AVCodec 初始化 AVCodecContext

//打开解码器 ret = avcodec_open2(ac, audioCodec, 0); if (ret != 0) { LOGE(“avcodec_open2 audioCodec failed …”); return; } ret = avcodec_open2(vc, vedieCodec, 0); if (ret != 0) { LOGE(“avcodec_open2 vedieCodec failed …”); return; }

2.2、开始解码流程

准备好解码器以及解码器上下文就可以开始解码流程了。在上一节中,我们已经通过 av_read_frame解封装获取到对应的编码数据包 AvPacket,下面我们要做的是解码这个数据包。

还是列一下操作步骤:

0、av_read_frame得到解封装后的 AvPacket 。 1、avcodec_send_packet 将 AvPacket 送入解码队列。 2、avcodec_receive_frame 得到解码后的 AvFrame 数据。注意:在 avcodec_send_packet之后,可能有多个 AvFrame 可以读取,因此在读取时需要循环读取。 //临时存储的解码器上下文 AVCodecContext *cc = NULL; //视频解码器 AVCodecContext *vc = NULL; //视频解码器 AVCodecContext *ac = NULL; //得到解码器并初始化解码器上下文 … //开始解码 for(;;){ //得到解封装后的 AvPacket ret = av_read_frame(avFormatContext, pkt); if(ret!=0){ continue; } if(pkt->stream_index == audioIndex){//当前解码音频数据 cc = ac; }else if(pkt->stream_index == videoIndex){//当前解码视频帧 cc = vc; } //将 AvPacket 送入给解码队列 ret = avcodec_send_packet(cc, pkt); //得到解码后的 AvFrame 数据 //发送一个 avpacket 之后可能可以收到多个 avframe for(;;){ ret = avcodec_receive_frame(cc, avFrame); if (ret != 0) { break; } //TODO 在这里可以处理解码后的数据拉,例如滤镜操作,像素格式转化,重采样等。 } } //释放资源 avcodec_free_context(&ac); avcodec_free_context(&vc);
<

注意:

在 avcodec_send_packet与avcodec_receive_frame应该是异步操作的,avcodec_send_packet 会将 AvPacket 放入到缓存队列中去解码,avcodec_receive_frame初次被调用时因为异步的原因可能没有获取到,也有可能可以获取多个 AvFrame,主要还是依赖解码的速度,因此通过循环去调用 avcodec_receive_frame 是比较妥当的做法。

接下来,对照着上面的流程,使用代码来实现 FFmpeg 的解码流程。

3.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 方法中执行。

3.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; }

3.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; } } }
<

示例

示例

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帧

免责声明:文章内容来自互联网,本站不对其真实性负责,也不承担任何法律责任,如有侵权等情况,请与本站联系删除。
转载请注明出处:Android 利用 FFmpeg 解码音视频数据 https://www.yhzz.com.cn/a/12069.html

上一篇 2023-05-01
下一篇 2023-05-01

相关推荐

联系云恒

在线留言: 我要留言
客服热线:400-600-0310
工作时间:周一至周六,08:30-17:30,节假日休息。