FFmpeg 解码音视频实例及碰到的问题记录(二)
程序员文章站
2022-04-24 14:33:43
...
最近项目开发中需要使用FFmpeg进行音视频的解码,在使用过程中遇到了一些问题对其进行记录。
FFmpeg版本:FFmpeg3.4.1 下载地址如下:https://download.csdn.net/download/lifei092/10670992
1、音频解码:
- av_register_all(); 遍历注册所有的组件,包括各种编解码器、解复用器等等;
- AVFormatContext *pFormatCtx = avformat_alloc_context(); 主要存储视音频封装格式中包含的信息,此变量非常重要,几乎贯穿了解码过程的始终,此后主要位pFormatCtx变量分配内存;
- avformat_open_input(&pFormatCtx, pInput_test, NULL, NULL); 打开输入视频文件,读取文件头信息,将文件头信息读取到pFormatCtx 结构体内,为后续解码做准备;
- avformat_find_stream_info(pFormatCtx, NULL); 获取文件流信息,主要根据pFormatCtx结构体内的已有信息对pFormatCtx结构体内流字段赋值流信息,即,可看作进一步为pFormatCtx结构体进行赋值;
- AVCodecParameters *pAudioCodecCtx= pFormatCtx->streams[a_stream_idx]->codecpar; 提取pFormatCtx结构体内的对应流编码器信息
- AVCodec *pAudioCodec= avcodec_find_decoder(pAudioCodecCtx->codec_id); 根据编解码的编码id号查找对应的音频解码器信息
- AVCodecContext *pAudioEnc = avcodec_alloc_context3(pAudioCodec); 根据初始化AVCodecContext,只是分配,还没打开
- avcodec_parameters_to_context(pAudioEnc, pFormatCtx->streams[a_stream_idx]->codecpar); 填充CodecContext信息,此步骤对于解码音频十分重要,若无此步骤,则无法进行正常的音频解码
- avcodec_open2(pAudioEnc , pAudioCodec, NULL); 打开解码器
- av_read_frame(pFormatCtx, packet); 从pFormatCtx结构体内流字段读取视频文件压缩流到packet变量内
- avcodec_send_packet(pAudioEnc , packet); 将packet内文件压缩流导入到对应解码器内
- avcodec_receive_frame(pAudioEnc , pFrame); 从解码器pAudioEnc 内解码压缩流到pFrame变量内(存储解压缩后的音视频数据)
- pFrame结构体内的 uint8_t *data[AV_NUM_DATA_POINTERS]; 成员内存储了音视频流解码后的数据,可对其进行相应操作(可保存可进行图像处理等等)。
以下是示例代码(由于从项目中扣取出来,大致流程无问题,未进行验证可能会有一些小问题,现先记录后续再进行验证):
#include <windows.h>
#include <stdint.h>
#include <iostream>
extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/channel_layout.h"
#include "libavutil/common.h"
#include "libavutil/imgutils.h"
#include "libswscale/swscale.h"
#include "libavutil/imgutils.h"
#include "libavutil/opt.h"
#include "libavutil/mathematics.h"
#include "libavutil/samplefmt.h"
};
#define ES_STREAM_VIDEO 1
#define ES_STREAM_AUDIO 2
#pragma comment(lib, "FFmpeg/lib/avcodec.lib")
#pragma comment(lib, "FFmpeg/lib/avformat.lib")
#pragma comment(lib, "FFmpeg/lib/avdevice.lib")
#pragma comment(lib, "FFmpeg/lib/avfilter.lib")
#pragma comment(lib, "FFmpeg/lib/avutil.lib")
#pragma comment(lib, "FFmpeg/lib/swresample.lib")
#pragma comment(lib, "FFmpeg/lib/swscale.lib")
typedef struct AVMediaPacket
{
BYTE* m_data[TL_NUM_DATA_POINTERS];
int m_linesize[TL_NUM_DATA_POINTERS];
BYTE* m_pBuf;
int m_buf_size;
int m_max_size;
int m_cur_size;//ES包使用
int m_packet_type;//0ES包 1Frame
//enum AVPixelFormat for video frames
//enum AVSampleFormat for audio
int m_pixel_format;
int m_channel_count;
LONGLONG m_channel_layout;
int m_nb_samples; // 单个声道音频样本个数
int m_sample_rate; // 音频采样率
int m_width;
int m_height;
/**
* The content of the picture is interlaced.
* - encoding: Set by user.
* - decoding: Set by libavcodec. (default 0)
*/
int m_interlaced_frame;//0逐行帧,1隔行帧
/**
* If the content is interlaced, is top field displayed first.
* - encoding: Set by user.
* - decoding: Set by libavcodec.
*/
int m_top_field_first;//0偶底场优先,1上奇场优先
int m_pict_type;
int m_es_stream_type;//是视频帧还是音频帧
LONGLONG m_pos;
LONGLONG m_origin_size;//原始ES包的字节数
LONGLONG m_dts;//ES包使用
LONGLONG m_pts;
LONGLONG m_origin_ts;
LONGLONG m_sys_pts;//转换成系统时间的pts(毫秒)
LONGLONG m_duration;//帧时长
LONGLONG m_sys_duration;//转换成系统时间的长度(毫秒)
int m_align;//数据的对齐方式
int m_scale_mod;//视频帧用到的变换方式(该帧是经过该变换方式得来的)
int m_stream_index;//同AVPacket的stream_index
int m_flags;//同AVPacket的flags
}AVMediaPacket;
/********** 查询FFmpeg支持的编解码器 ************/
void CheckEncoderDecoder()
{
char *info = (char *)malloc(40000);
memset(info, 0, 40000);
AVCodec *c_temp = av_codec_next(NULL);
while (c_temp != NULL)
{
if (c_temp->decode != NULL)
{
strcat(info, "[Decode]");
}
else
{
strcat(info, "[Encode]");
}
switch (c_temp->type)
{
case AVMEDIA_TYPE_VIDEO:
strcat(info, "[Video]");
break;
case AVMEDIA_TYPE_AUDIO:
strcat(info, "[Audeo]");
break;
default:
strcat(info, "[Other]");
break;
}
sprintf(info, "%s %10s\n", info, c_temp->name);
c_temp = c_temp->next;
}
puts(info);
free(info);
}
void InitMediaPacket(AVMediaPacket* pFrame,int nPacketType, int nEsStreamType)
{
for (int i = 0; i < TL_NUM_DATA_POINTERS; i++)
{
pFrame->m_data[i] = NULL; // 音视频数据
pFrame->m_linesize[i] = 0; // 每行数据的size
}
pFrame->m_pBuf = NULL; // 音视频数据指针
pFrame->m_buf_size = 0;
pFrame->m_max_size = 0;
pFrame->m_cur_size = 0;//ES包使用
pFrame->m_packet_type = nPacketType;//0ES包 1Frame
//enum AVPixelFormat for video frames
//enum AVSampleFormat for audio
pFrame->m_pixel_format = AV_PIX_FMT_NONE;
pFrame->m_channel_count = -1;
pFrame->m_channel_layout = -1;
pFrame->m_nb_samples = -1; // 单个声道音频样本个数
pFrame->m_sample_rate = -1; // 音频采样率
pFrame->m_width = -1;
pFrame->m_height = -1;
/**
* The content of the picture is interlaced.
* - encoding: Set by user.
* - decoding: Set by libavcodec. (default 0)
*/
pFrame->m_interlaced_frame = -1;//0逐行帧,1隔行帧
/**
* If the content is interlaced, is top field displayed first.
* - encoding: Set by user.
* - decoding: Set by libavcodec.
*/
pFrame->m_top_field_first = -1;//0偶底场优先,1上奇场优先
pFrame->m_pict_type = -1;
pFrame->m_es_stream_type = nEsStreamType;//是视频帧还是音频帧
pFrame->m_pos = -1;
pFrame->m_origin_size = -1;//原始ES包的字节数
pFrame->m_dts = -1;//ES包使用
pFrame->m_pts = -1;
pFrame->m_origin_ts = -1;
pFrame->m_sys_pts = -1;//转换成系统时间的pts(毫秒)
pFrame->m_duration = -1;//帧时长
pFrame->m_sys_duration = -1;//转换成系统时间的长度(毫秒)
pFrame->m_align = -1;//数据的对齐方式
pFrame->m_scale_mod = -1;//视频帧用到的变换方式(该帧是经过该变换方式得来的)
pFrame->m_stream_index = -1;//同AVPacket的stream_index
pFrame->m_flags = -1;//同AVPacket的flags
}
BOOL MallocMediaFrameBuf(AVMediaPacket* pFrame, int nBufLen)
{
if (nBufLen <= 0)
return FALSE;
for (int i = 0; i < TL_NUM_DATA_POINTERS; i++)
{
pFrame->m_data[i] = NULL; // 音视频数据
pFrame->m_linesize[i] = 0; // 每行数据的size
}
pFrame->m_pBuf = (BYTE*)malloc(nBufLen*sizeof(BYTE));
pFrame->m_buf_size = nBufLen;
if (pFrame->m_pBuf)
return TRUE;
else
return FALSE;
}
void ReleaseMediaPacket(AVMediaPacket* pFrame)
{
if (pFrame->m_pBuf)
{
free(pFrame->m_pBuf);
pFrame->m_pBuf = NULL;
}
}
int main()
{
const char *pInput_test = "E:\\C++\\VideoExaminationSystem\\彩条\\田野风测试1.gxf";
FILE *fout = fopen("E://test.yuv", "wb+");
AVMediaPacket src_AudioFrame;
//1.注册所有组件
av_register_all();
CheckEncoderDecoder(); // 查询此FFmpeg版本支持的编解码器
//封装格式上下文,统领全局的结构体,保存了视频文件封装格式的相关信息
AVFormatContext *pFormatCtx = avformat_alloc_context(); //主要存储视音频封装格式中包含的信息
//2.打开输入视频文件,读取文件头信息
if (avformat_open_input(&pFormatCtx, pInput_test, NULL, NULL) != 0)
{
printf("%s", "无法打开输入视频文件");
return -1;
}
//3.获取视频文件信息
if (avformat_find_stream_info(pFormatCtx, NULL) < 0)
{
printf("%s", "无法获取视频文件信息");
return -1;
}
//获取视频流的索引位置
//遍历所有类型的流(音频流、视频流、字幕流),找到视频流
int a_stream_idx = -1; // 音频流
//number of streams
for (int i = 0; i < pFormatCtx->nb_streams; i++)
{
//流的类型
if (pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
{
a_stream_idx = i; // 记录音频流索引
break;
}
}
if (a_stream_idx == -1 && (AUDIO_DETECT_ONLY == nDetectFlag || AV_DETECT == nDetectFlag))
{
printf("%s", "找不到音频流\n");
return -1;
}
//只有知道视频的编码方式,才能够根据编码方式去找到解码器
//获取视频流中的编解码上下文
AVCodecParameters *pAudioCodecCtx = NULL;
AVCodec *pAudioCodec = NULL;
AVCodecContext *pAudioEnc = NULL;
AVPacket *packet = NULL;
AVFrame *pFrame = NULL;
int src_audio_buf_size = -1;
int nb_planes = -1;
pAudioCodecCtx = pFormatCtx->streams[a_stream_idx]->codecpar;
//4.根据编解码上下文中的编码id查找对应的解码
pAudioCodec = avcodec_find_decoder(pAudioCodecCtx->codec_id);
if (pAudioCodec == NULL)
{
printf("%s", "找不到音频解码器\n");
return -1;
}
pAudioEnc = avcodec_alloc_context3(pAudioCodec); // 初始化AVCodecContext,只是分配,还没打开
/****************** START --> 此步骤非常重要: 不进行设置无法进行正常解码 ****************/
// 填充CodecContext信息
if (avcodec_parameters_to_context(pAudioEnc, pFormatCtx->streams[a_stream_idx]->codecpar) < 0)
{
printf("Failed to copy codec parameters to decoder context!\n");
return -1;
}
/****************** END --> 此步骤非常重要: 不进行设置无法进行正常解码 ****************/
//5.打开解码器
if (avcodec_open2(pAudioEnc, pAudioCodec, NULL)<0)
{
printf("%s", "音频解码器无法打开\n");
return -1;
}
//输出音频信息
printf("音频采样率:%d\n", pAudioCodecCtx->sample_rate);
//printf("音频帧尺寸:%d\n", pAudioCodecCtx->frame_size);
printf("音频的通道:%d\n", pAudioCodecCtx->channels);
printf("音频解码器的名称:%s\n", pAudioCodec->name);
std::cout << std::endl;
src_AudioFrame.m_pixel_format = pAudioCodecCtx->format;
src_AudioFrame.m_channel_count = pAudioCodecCtx->channels;
src_AudioFrame.m_sample_rate = pAudioCodecCtx->sample_rate;
//src_AudioFrame.m_nb_samples = pAudioCodecCtx->frame_size;
nb_planes = av_sample_fmt_is_planar((AVSampleFormat)pAudioCodecCtx->format) ? pAudioCodecCtx->channels : 1; //如果是 planar类型,需要分配一个指针数组,每个元素指向一个声道
src_audio_buf_size = AVCODEC_MAX_AUDIO_FRAME_SIZE * 4;
if (!MallocMediaFrameBuf(&src_AudioFrame, src_audio_buf_size))
{
return NULL;
}
// 根据音频格式填充结构体相关字段
//av_samples_fill_arrays(src_AudioFrame.m_data, src_AudioFrame.m_linesize, src_AudioFrame.m_pBuf, src_AudioFrame.m_channel_count, src_AudioFrame.m_nb_samples, (AVSampleFormat)src_AudioFrame.m_pixel_format, 1);
//准备读取
//缓冲区,开辟空间,AVPacket用于存储一帧一帧的压缩数据(H264)
packet = (AVPacket*)av_malloc(sizeof(AVPacket));
//内存分配, AVFrame用于存储解码后的像素数据(YUV)
pFrame = av_frame_alloc();
int ret = -1;
int audio_frame_count = 0;
//6.一帧一帧的读取压缩数据
while (av_read_frame(pFormatCtx, packet) >= 0)
{
//只要音频压缩数据(根据流的索引位置判断)
if (packet->stream_index == a_stream_idx && (AUDIO_DETECT_ONLY == nDetectFlag || AV_DETECT == nDetectFlag))
{
//7.解码一帧视频压缩数据,得到视频像素数据
ret = avcodec_send_packet(pAudioEnc, packet);
if (ret < 0)
{
printf("%s\n", "音频解码错误");
return -1;
}
ret = avcodec_receive_frame(pAudioEnc, pFrame);
src_AudioFrame.m_nb_samples = pFrame->nb_samples;
// 根据音频格式填充结构体相关字段
av_samples_fill_arrays(src_AudioFrame.m_data, src_AudioFrame.m_linesize, src_AudioFrame.m_pBuf, src_AudioFrame.m_channel_count, src_AudioFrame.m_nb_samples, (AVSampleFormat)src_AudioFrame.m_pixel_format, 1);
if (!ret) // 0表示解码成功
{
for (int j = 0; j < nb_planes; j++)
{
memcpy(src_AudioFrame.m_pBuf, pFrame->data[j], pFrame->linesize[0] * sizeof(BYTE));
fwrite(src_AudioFrame.m_data[j], pFrame->width*pFrame->height/(radio*radio), 1, fout);
}
audio_frame_count++;
printf("Decode Audio Frame Number : %d\n", audio_frame_count);
}
}
//释放资源
av_packet_unref(packet);
}
// Free the YUV frame
av_frame_free(&pFrame);
// Close the codecs
avcodec_close(pAudioEnc);
// Close the video file
avformat_close_input(&pFormatCtx);
ReleaseMediaPacket(&src_AudioFrame);
std::cout << "I: Finish Video Scale." << std::endl;
system("pause");
return 0;
}
在调试过程中遇到了以下问题:
- 对于音频解码过程中,需注意音频数据是否为planar格式,若为planar格式则解码后的音频数据在AVFrame结构体中多个通道中,而若不为planar格式,则解码后的音频数据存放在AVFrame结构体中的data[0]中。判断是否为planar格式通过以下方式 nb_planes = av_sample_fmt_is_planar((AVSampleFormat)pAudioCodecCtx->format) ? pAudioCodecCtx->channels : 1; //如果是 planar类型,需要分配一个指针数组,每个元素指向一个声道
- 通常在解码视频时,通过avcodec_open2()函数后即可获取frame_size(数据帧大小);而音频解码过程中,发现通过avcodec_open2()函数后获取frame_size = 0; 这显然是有问题的,在调试过程中也确实遇到了无法解码音频数据的情况。解决方法为:通过avcodec_receive_frame(pAudioEnc, pFrame);函数之后,获取frame_size(数据帧大小),即src_AudioFrame.m_nb_samples = pFrame->nb_samples; 再根据音频格式填充结构体相关字段av_samples_fill_arrays();
上一篇: memset注意的地方
下一篇: WSAEventSelect模型