ffmpeg(4.0.2)解碼H264
介面變更:
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 <stdio.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#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;i<pFrame->height;i++){
fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
}
for(i=0;i<pFrame->height/2;i++){
fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
}
for(i=0;i<pFrame->height/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;i<pFrame->height;i++){
fwrite(pFrame->data[0]+pFrame->linesize[0]*i,1,pFrame->width,fp_out);
}
for(i=0;i<pFrame->height/2;i++){
fwrite(pFrame->data[1]+pFrame->linesize[1]*i,1,pFrame->width/2,fp_out);
}
for(i=0;i<pFrame->height/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檔案大小同壓縮前一致,代表解壓成功。但是發現最後一幀丟失,程式碼中已做了緩衝重新整理,具體原因還未查清。待有時間後續再改進,先做記錄。