相信很多人在刚接触ffmpeg的时候,想要ffmpeg的api都觉得很比较繁琐,因为本身代码量比较大,模块比较多,api也比较多,而且在ffmpeg中的例程都是以文件的行驶传入到编解码器重的,所以想实现简单的纯数据流解码就感觉无从下手了;本文就是描述将一帧完整的H264数据解码为yuyv格式的数据。
ffmpeg版本:ffmpeg-3.1.2
用到的ffmpeg库有:libavformat、libavcodec、libavutil
- 对外提供的接口有:
//初始化解码器,codec_id为选择的解码器 int InitDecoder(int codec_id); //入参:framedata、framelen为H264数据源和数据长度 //出参:outputframe为解码后的yuyv数据存放区,width/height/pixfmt返回视频宽/高/解码后的数据格式, int DecodeFrame(unsigned char * framedata , int framelen ,//input unsigned char *outputframe , int *width , int *height , int *pixfmt);//output //参数意义同上一接口,作用是将解码器中的缓存数据刷出 int DecodeFrameFlush(unsigned char *outputframe , int *width , int *height , int *pixfmt);//output //清理解码器申请的资源 void UninitDecoder(void);
- 头文件和变量:
#include <libavutil/imgutils.h> #include <libavutil/samplefmt.h> #include <libavutil/timestamp.h> #include <libavformat/avformat.h> #include "socket_lib.h" static AVCodec *codec = NULL; static AVCodecContext *codec_ctx= NULL; static AVFrame *frame = NULL; static AVPacket avpkt; static bool codecInited = false;
- 解码器初始化:
int InitDecoder(int codec_id) { if(codecInited != false) { printf("decoder inited fail\n"); return -1; } avcodec_register_all(); codec = avcodec_find_decoder(codec_id); if(!codec) { printf("avcodec_find_decoder fail\n"); return -1; } codec_ctx = avcodec_alloc_context3(codec); if(!codec_ctx) { printf("avcodec_alloc_context3 fail\n"); return -1; }/* else { codec_ctx->time_base.num = 1; codec_ctx->frame_number = 1; //每包一个视频帧 codec_ctx->codec_type = AVMEDIA_TYPE_VIDEO; codec_ctx->bit_rate = 0; codec_ctx->time_base.den = den;//帧率 codec_ctx->width = width;//视频宽 codec_ctx->height = height;//视频高 }*/ if(avcodec_open2(codec_ctx, codec, NULL) < 0) { printf("avcodec_open2 fail\n"); return -1; } frame = av_frame_alloc(); if(!frame) { printf("av_frame_alloc fail\n"); return -1; } av_init_packet(&avpkt); codecInited = true; return 0; }
- 解码一帧H264视频:
- 在成功解码后这个接口会返回这个视频的宽、高、解码后的数据格式(yuv420或者yuv422等等)
int DecodeFrame(unsigned char * framedata , int framelen ,//input unsigned char *outputframe , int *width , int *height , int *pixfmt)//output { avpkt.size = framelen; avpkt.data = framedata; while(avpkt.size > 0) { if(decode_frame(codec_ctx, frame, &avpkt, 0, outputframe) < 0) { printf("%s decode fail\n",__func__); return -1; } } *width = codec_ctx->width; *height = codec_ctx->height; *pixfmt = codec_ctx->pix_fmt; return 0; }
- 解码一帧详细操作(这里只是做了YUV422P的处理):
static int decode_frame(AVCodecContext *avctx,AVFrame *frame,//input AVPacket *pkt, int last,//input unsigned char *outputframe)//output { int len = 0; int got_frame = 0; if(!codecInited) { printf("%s decoder uninted\n",__func__); return -1; } len = avcodec_decode_video2(avctx, frame, &got_frame, pkt); if(len < 0) { printf("Error while decoding frames\n"); return len; } if(got_frame) { switch(avctx->pix_fmt) { case AV_PIX_FMT_YUV422P: { int index = 0; int y_i = 0 , u_i = 0 , v_i = 0; for(index = 0 ; index < frame->width*frame->height*2 ;) { outputframe[index++] = frame->data[0][y_i++]; outputframe[index++] = frame->data[1][u_i++]; outputframe[index++] = frame->data[0][y_i++]; outputframe[index++] = frame->data[2][v_i++]; } }break; default: { return -1; } } } if(pkt->data) { pkt->size -= len; pkt->data += len; } return 0; }
- 因为avcodec_decode_video2这个接口被ffmpeg所摒弃的,可以用新的接口://20180329 add
int DecodeFrame(unsigned char * framedata, int framelen,//input unsigned char *outputframe, int *width, int *height, int *pixfmt)//output { avpkt.size = framelen; avpkt.data = framedata; if (avcodec_send_packet(codec_ctx, &avpkt)) { printf("%s %d avcodec_send_packet fail\n",__func__,__LINE__); return -1; } if(avcodec_receive_frame(codec_ctx, frame)) { printf("%s %d avcodec_receive_frame fail\n", __func__, __LINE__); return -1; } switch (codec_ctx->pix_fmt) { case AV_PIX_FMT_YUV422P: { int index = 0; int y_i = 0, u_i = 0, v_i = 0; for (index = 0; index < frame->width*frame->height * 2;) { outputframe[index++] = frame->data[0][y_i++]; outputframe[index++] = frame->data[1][u_i++]; outputframe[index++] = frame->data[0][y_i++]; outputframe[index++] = frame->data[2][v_i++]; } }break; default: { printf("default format:%d\n", codec_ctx->pix_fmt); return -1; } } *width = codec_ctx->width; *height = codec_ctx->height; *pixfmt = codec_ctx->pix_fmt; return 0; }
- 在成功解码后这个接口会返回这个视频的宽、高、解码后的数据格式(yuv420或者yuv422等等)
- 解码完成后,刷出解码器中的数据:
int DecodeFrameFlush(unsigned char *outputframe , int *width , int *height , int *pixfmt)//output { if(!codecInited) { printf("%s decoder uninted\n",__func__); return -1; } avpkt.data = NULL; avpkt.size = 0; if(decode_frame(codec_ctx, frame, &avpkt, 1, outputframe) < 0) return -1; *width = codec_ctx->width; *height = codec_ctx->height; *pixfmt = codec_ctx->pix_fmt; return 0; }
- 释放解码器的资源:
void UninitDecoder(void) { if(codecInited) { avcodec_close(codec_ctx); av_free(codec_ctx); av_frame_free(&frame); codecInited = false; } }
这里有相关资源的下载:
http://download.youkuaiyun.com/download/xushan239/10199075
http://download.youkuaiyun.com/download/xushan239/10190449