接口变更:
AVStream的codec参数被codecpar参数所替代
AVCodecContext *codec变为AVCodecParameters *codecpar
av_register_all被弃用
添加av_demuxer_iterate()
const AVInputFormat *av_demuxer_iterate(void **opaque);
解码接口变更:
视频解码接口avcodec_decode_video2和avcodec_decode_audio4音频解码deprecated,两个接口做了合并,使用统一的接口。将音视频解码步骤分为两步:
第一步:avcodec_send_packet() 发送编码数据包
第二步:avcodec_receive_frame() 接收解码后数据
函数原型:
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt);
参数:
AVCodecContext *avctx:视频解码的上下文,包含解码器。
const AVPacket *avpkt: 编码的音视频帧数据
返回值:
成功返回0
为什么要传递空的avpkt?
这里有一个说明是可以传递NULL,什么情况下需要传递NULL,你平时看一些视频播放器,播放经常会少最后几帧,很多情况就是因为没有处理好缓冲帧的问题,ffmpeg内部会缓冲几帧,要想取出来就需要传递空的AVPacket进去。
函数原型:
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
参数:
AVCodecContext *avctx:视频解码的上下文,包含解码器。
AVFrame *frame:解码后的视频帧数据。
返回值:
成功返回0
*******空间申请和释放问题*********
解码后图像空间由函数内部申请,你所做的只需要分配 AVFrame 对象空间,如果你每次调用avcodec_receive_frame传递同一个对象,接口内部会判断空间是否已经分配,如果没有分配会在函数内部分配。
avcodec_send_packet和avcodec_receive_frame调用关系并不一定是一对一的,比如一些音频数据一个AVPacket中包含了1秒钟的音频,调用一次avcodec_send_packet之后,可能需要调用25次 avcodec_receive_frame才能获取全部的解码音频数据,所以要做如下处理:
int ret = avcodec_send_packet(codec, pkt);
if (ret != 0)
{
return;
}
while( avcodec_receive_frame(codec, frame) == 0)
{
//读取到一帧音频或者视频
//处理解码后音视频 frame
}
源代码:
/**基于雷神(雷霄骅)博文修改
* 基于FFmpeg的视频解码器
* Simplest FFmpeg Decoder Pure
*
* 本程序实现了视频码流H.264解码为YUV数据。
* 通过学习本例子可以了解FFmpeg的解码流程。
*/
#include
#include
#include
#define __STDC_CONSTANT_MACROS
int main(int argc, char* argv[])
{
AVCodec *pCodec;
const int in_buffer_size=4096;
uint8_t in_buffer[4096 + 460800]={0};
uint8_t *cur_ptr;
int cur_size;
int ret,i,got_picture;
int y_size;
AVFrame *pFrame; //AVFrame存储一帧解码后的像素数据
AVPacket packet; //存储一帧(一般情况下)压缩编码数据
enum AVCodecID codec_id=AV_CODEC_ID_H264;
char filepath_in[]="yuv_640x480.h264";
char filepath_out[]="decode_yuv420p_640x480.yuv";
int first_time=1;
//注册所有的编解码器
void *opaque = NULL;
av_demuxer_iterate(&opaque);
//av_register_all(); 被弃用
//打开多媒体文件
AVFormatContext *pFormatCtx = NULL;
//为AVFormatContext分配内存
pFormatCtx=avformat_alloc_context();
if(avformat_open_input(&pFormatCtx, filepath_in, NULL, NULL) != 0){
return -1; // Couldn't open file
}
//独立的解码上下文
//AVCodecContext视频解码的上下文,为AVCodecContext分配内存
AVCodecContext *pCodecCtx = avcodec_alloc_context3(pCodec);
if (!pCodecCtx){
printf("Could not allocate video codec context\n");
return -1;
}
//循环遍历所有流,找到视频流
int videoStream = -1;
for(i = 0; i < pFormatCtx->nb_streams; i++) {
if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
videoStream = i;
break;
}
}
//将配置参数复制到AVCodecContext中
avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[videoStream]->codecpar);
//查找视频解码器
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
if (!pCodec) {
printf("Codec not found\n");
return -1;
}
//打开解码器
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
printf("Could not open codec\n");
return -1;
}
//初始化AVCodecParserContext
AVCodecParserContext *pCodecParserCtx=NULL;
pCodecParserCtx=av_parser_init(codec_id);
if (!pCodecParserCtx){
printf("Could not allocate video parser context\n");
return -1;
}
//Input File
FILE *fp_in = fopen(filepath_in, "rb");
if (!fp_in) {
printf("Could not open input stream\n");
return -1;
}
//Output File
FILE *fp_out = fopen(filepath_out, "wb");
if (!fp_out) {
printf("Could not open output YUV file\n");
return -1;
}
//为AVFrame分配内存
pFrame = av_frame_alloc();
//初始化AVPacket
av_init_packet(&packet);
int n_frame=0;
while (1) {
cur_size = fread(in_buffer, 1, in_buffer_size, fp_in);
if (cur_size == 0)
break;
cur_ptr=in_buffer;
while (cur_size>0){
//解析获得一个Packet
int len = av_parser_parse2(
pCodecParserCtx, pCodecCtx,
&packet.data, &packet.size,
cur_ptr , cur_size ,
AV_NOPTS_VALUE, AV_NOPTS_VALUE, AV_NOPTS_VALUE);
cur_ptr += len;
cur_size -= len;
if(packet.size==0)
continue;
//Some Info from AVCodecParserContext
printf("[Packet]Size:%6d\t",packet.size);
switch(pCodecParserCtx->pict_type){
case AV_PICTURE_TYPE_I: printf("Type:I\t");break;
case AV_PICTURE_TYPE_P: printf("Type:P\t");break;
case AV_PICTURE_TYPE_B: printf("Type:B\t");break;
default: printf("Type:Other\t");break;
}
printf("Number:%4d\n",pCodecParserCtx->output_picture_number);
//解码一帧数据
//avcodec_send_packet()向解码器提供输入AVPacket
ret = avcodec_send_packet(pCodecCtx, &packet);
if (ret != 0)
{
return;
}
//avcodec_receive_frame()接收解码的帧AVFrame
while( avcodec_receive_frame(pCodecCtx, pFrame) == 0)
{
got_picture=1;
if(got_picture){
//读取到一帧视频,处理解码后视频frame
if(first_time){
printf("\nCodec Full Name:%s\n",pCodecCtx->codec->long_name);
printf("width:%d\nheight:%d\n\n",pCodecCtx->width,pCodecCtx->height);
first_time=0;
}
//Y, U, V
int i;
for(i=0;iheight;i++){
fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
}
for(i=0;iheight/2;i++){
fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
}
for(i=0;iheight/2;i++){
fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
}
n_frame++;
printf("Succeed to decode %d frame!\n",n_frame);
}
}
}
}
//Flush Decoder
packet.data = NULL;
packet.size = 0;
while(1){
ret = avcodec_receive_frame(pCodecCtx, pFrame);
if (ret < 0) {
printf("Decode Error.\n");
return ret;
}
if (!got_picture){
break;
}else {
//Y, U, V
int i;
for(i=0;iheight;i++){
fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
}
for(i=0;iheight/2;i++){
fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
}
for(i=0;iheight/2;i++){
fwrite(pFrame->data[2]+pFrame->linesize[2]*i,1,pFrame->width/2,fp_out);
}
printf("Flush Decoder: Succeed to decode 1 frame!\n");
}
}
fclose(fp_in);
fclose(fp_out);
//释放AVFormatContext和它所有的流
avformat_free_context(pFormatCtx);
av_parser_close(pCodecParserCtx);
av_frame_free(&pFrame);
avcodec_close(pCodecCtx);
av_free(pCodecCtx);
return 0;
}
解码出来,yuv文件大小同压缩前一致,代表解压成功。但是发现最后一帧丢失,代码中已做了缓冲刷新,具体原因还未查清。待有时间后续再改进,先做记录。