FFmpeg編程(二)FFmpeg中級開發


一:H264解碼處理

(一)解碼步驟

1.引入解碼頭文件

#include <libavcodec/avcodec.h>

2.常用數據結構

AVCodec編碼器結構體:          所使用的編碼器類型,(H264/H265,音頻/視頻)
AVCodecContext編碼器上下文: 串聯各個API,形成API鏈條,每個API都需要我們把上下文作為參數傳入該API。內部也保存了編解碼器信息,可以供其調用
AVFrame解碼后的幀: 未壓縮的幀(未編碼)

3.結構體內存的分配和釋放

4.解碼步驟

avcodec_find_decoder通過id查找解碼器,當然也可以通過名字by_name查找到編解碼器(如下面的編碼);兩種方式各有好處
avcodec_decode_video2編解碼一般使用外部編解碼庫,比如libx264;

注意:avcodec_decode_video2與后面的avcodec_decode_audio4函數解碼:是指從packet中解析出來一幀一幀數據,並不涉及數據格式的轉換,如果要進行格式的轉換,需要設置重采樣方法等等!!

(二)編程實戰(YUV視頻流轉RGB圖像)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswscale/swscale.h>    //ibswscale是一個主要用於處理圖片像素數據的類庫。可以完成圖片像素格式的轉換,圖片的拉伸等工作

#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t

//https://www.cnblogs.com/lzlsky/archive/2012/08/16/2641698.html
typedef struct tagBITMAPFILEHEADER {
  WORD  bfType;            //位圖類別,根據不同的操作,系統而不同,在Windows中,此字段的值總為‘BM’
  DWORD bfSize;            //BMP圖像文件的大小
  WORD  bfReserved1;    //保留,為0
  WORD  bfReserved2;    //保留,為0
  DWORD bfOffBits;        //BMP圖像數據的地址
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;

typedef struct tagBITMAPINFOHEADER {
  DWORD biSize;            //包含的是這個結構體的大小(包括顏色表)
  LONG  biWidth;        //是圖片的長
  LONG  biHeight;        //是圖片的寬
  WORD  biPlanes;        //是目標繪圖設備包含的層數,必須設置為1
  WORD  biBitCount;        //是圖像的位數,例如24位,8位等
  DWORD biCompression;    //壓縮方式,0表示不壓縮,1表示RLE8壓縮,2表示RLE4壓縮,3表示每個像素值由指定的掩碼決定
  DWORD biSizeImage;    //BMP圖像數據大小,必須是4的倍數,圖像數據大小不是4的倍數時用0填充補足
  LONG  biXPelsPerMeter;//水平分辨率,單位像素/m
  LONG  biYPelsPerMeter;//垂直分辨率,單位像素/m
  DWORD biClrUsed;        //BMP圖像使用的顏色,0表示使用全部顏色,對於256色位圖來說,此值為100h=256
  DWORD biClrImportant;    //重要的顏色數,此值為0時所有顏色都重要,對於使用調色板的BMP圖像來說,當顯卡不能夠顯示所有顏色時,此值將輔助驅動程序顯示顏色
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;

void saveBMP(struct SwsContext* img_convert_cxt,AVFrame* frame,char* filename){
    //1.先進行轉換,YUV420=>RGB24
    int w = frame->width;
    int h = frame->height;

    int numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,w,h);    //計算字節數量
    uint8_t* buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t));    //分配空間

    AVFrame* pFrameRGB = av_frame_alloc();
    //avpicture_fill函數將ptr指向的數據填充到picture內,但並沒有拷貝,只是將picture結構內的data指針指向了ptr的數據。
    avpicture_fill((AVPicture*)pFrameRGB,buffer,AV_PIX_FMT_RGB24,w,h);
    //真正用來做轉換的函數:https://www.cnblogs.com/yongdaimi/p/10715830.html
    sws_scale(img_convert_cxt,frame->data,frame->linesize,    //當前處理區域的每個通道數據指針,每個通道行字節數
        0,h,    //參數int srcSliceY, int srcSliceH,定義在輸入圖像上處理區域,srcSliceY是起始位置,srcSliceH是處理多少行。如果srcSliceY=0,srcSliceH=height,表示一次性處理完整個圖像。
        pFrameRGB->data,pFrameRGB->linesize);    //定義輸出圖像信息(輸出的每個通道數據指針,每個通道行字節數)

    //上面將數據轉換完成,下面初始化結構體,寫入BMP圖片數據,到文件中去
    //---構造BITMAPINFOHEADER信息首部(第二部分,先有文件頭,再有信息頭,再之后是數據)
    BITMAPINFOHEADER header;

    header.biSize = sizeof(BITMAPINFOHEADER);
    header.biWidth = w;
    /*
    如果該值是一個正數,說明Btimap是Bottom up DIB,起始點是左下角,也就是從圖像的最下面一行掃描,位圖數組中得到的第一行數據實際是圖形的最下面的一行。圖像是倒向的;
    如果該值是一個負數,則說明圖像是TopDown DIB,起始點是左上角,圖像從最上面一行掃描,圖像正向的。
    大多數的BMP文件都是倒向的位圖,也就是時,高度值是一個正數。(注:當高度值是一個負數時(正向圖像),圖像將不能被壓縮(也就是說biCompression成員將不能是BI_RLE8或BI_RLE4)
    */
    header.biHeight = h*(-1);            //biHeight字段的正負號指定DIB圖像的繪制方向,負數表示為正向,不被壓縮
    header.biPlanes = 1;                //必須為1
    header.biBitCount = 24;                //RBG位深24
    header.biCompression = 0;            //不壓縮
    header.biSizeImage = 0;                //其中 biSizeImage 如果不為 0 這代表位圖中實際的像素數據字節數;同時如果為0,位圖像素數據的字節數也可以通過 biWidth biHeight biBitCount 計算得到。
    header.biXPelsPerMeter = 0;            //設置分辨率 像素/米
    header.biYPelsPerMeter = 0;    
    header.biClrUsed = 0;                //使用全部顏色
    header.biClrImportant = 0;            //全都重要

    //---BITMAPFILEHEADER文件頭(第一部分)
    BITMAPFILEHEADER bmpFileHeader;
    bmpFileHeader.bfType = 0x4d42;    //"BM"
    bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+numBytes;
    bmpFileHeader.bfReserved1 = 0;
    bmpFileHeader.bfReserved2 = 0;
    bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER);

    FILE* fp = fopen(filename,"wb");
    //由於linux上4字節對齊,而信息頭大小為54字節,第一部分14字節,第二部分40字節,所以會將第一部分補齊為16自己,直接用sizeof,打開圖片時就會遇到premature end-of-file encountered錯誤  
    //下面的方式可以防止對齊,避免對齊導致出錯
    fwrite(&bmpFileHeader,8,1,fp);    //先把bfType、bfSize、bfReserved1寫入
    fwrite(&bmpFileHeader.bfReserved2,sizeof(bmpFileHeader.bfReserved2),1,fp);
    fwrite(&bmpFileHeader.bfOffBits,sizeof(bmpFileHeader.bfOffBits),1,fp);

    fwrite(&header,sizeof(BITMAPINFOHEADER),1,fp);
    
    //-----------進行色彩矯正,RGB-->GBR才能變為原本的色彩
    for(int i=0;i<numBytes-3;i+=3){
        uint8_t temp = pFrameRGB->data[0][i];
        pFrameRGB->data[0][i] = pFrameRGB->data[0][i+1];
        pFrameRGB->data[0][i+1] = temp;

        temp = pFrameRGB->data[0][i+1];
        pFrameRGB->data[0][i+1] = pFrameRGB->data[0][i+2];
        pFrameRGB->data[0][i+2] = temp;
    }

    fwrite(pFrameRGB->data[0],1,numBytes,fp);    //對於RGB只需要一個數組,YUV需要3個
    fclose(fp);

    //釋放資源
    av_freep(&pFrameRGB[0]);
    av_freep(pFrameRGB);
}

int decode_write_frame(const char* out_filename,AVCodecContext* avctx,struct SwsContext* img_convert_cxt,
                        AVFrame* frame,int* frame_count,AVPacket* packet,int last){
    int len,got_frame;
    char buf[1024];

    //進行解碼操作-----------------
    //作用是解碼一幀視頻數據。輸入一個壓縮編碼的結構體AVPacket,輸出一個解碼后的結構體AVFrame。
    len = avcodec_decode_video2(avctx,frame,&got_frame,packet);    //got_frame該值為0表明沒有圖像可以解碼,否則表明有圖像可以解碼;
    if(len<0){
        av_log(NULL,AV_LOG_ERROR,"Fail to decode video frame %d\n",*frame_count);
        return len;
    }

    if(got_frame){    //一個包中可能有1個或者多個幀,一般視頻包中包含1幀;這里我們只獲取1幀,進行處理,其他的依舊保留在packet結構體中,后面進行修改
        if(last)
            av_log(NULL,AV_LOG_INFO,"----Get last frame");    //一般最后一幀可能會做特殊處理
        av_log(NULL,AV_LOG_INFO,"Get frame count %3d,Saving frame %3d\n",got_frame,*frame_count);
        snprintf(buf,sizeof(buf),"%s-%d.bmp",out_filename,*frame_count);    //圖像命名

        //保存圖像
        saveBMP(img_convert_cxt,frame,buf);
        (*frame_count)++;
    }

    //avcodec_decode_video2只獲取了包中的一幀,然而包中可能還有其他幀,所以這里進行處理,進行結構體數據修改
    if(packet->data){
        packet->size -= len;    //大小減去1幀大小
        packet->data += len;    //數據下移,跳過已經處理的數據
    }
    return 0;
}

void decode_video(char* in_filename,char* out_filename){
    int ret,cnt=500;    //cnt長視頻輸出數量

    AVFormatContext* fmt_cxt = NULL;

    AVCodec* codec = NULL;
    AVCodecContext* c = NULL;

    struct SwsContext* img_convert_cxt = NULL;    //圖像處理上下文

    int stream_idx;
    AVStream* st = NULL;

    int frameCnt = 0;    //記錄解碼的幀數量
    AVFrame* frame = NULL;

    AVPacket packet;

    ret = avformat_open_input(&fmt_cxt,in_filename,NULL,NULL);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t open input file:%s\n",in_filename);
        return;
    }

    ret = avformat_find_stream_info(fmt_cxt,0); //獲取輸入流的詳細信息
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Could not find stream information\n");
        goto __AVFORMAT;
    }
    
    //媒體文件句柄 / 流類型 / 請求的流編號(-1則自動去找) / 相關流索引號(比如音頻對應的視頻流索引號),不指定則-1 / 如果非空,則返回所選流的解碼器(指針獲取) / flag當前未定義
    ret = av_find_best_stream(fmt_cxt,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t find the best stream\n");
        goto __AVFORMAT;
    }

    //獲取流並打印流的信息
    stream_idx = ret;
    st = fmt_cxt->streams[stream_idx];

    av_dump_format(fmt_cxt,stream_idx,in_filename,0);

    //查找解碼器-----------------
    codec = avcodec_find_decoder(st->codecpar->codec_id);    //根據id查找解碼器,id信息存放在輸入文件視頻流中
    if(!codec){
        av_log(NULL,AV_LOG_ERROR,"Can`t find the decoder[%s] for input file:%s\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO),in_filename);
        goto __AVFORMAT;
    }

    //打開解碼器之前,先分配上下文內存-----------------
    c = avcodec_alloc_context3(NULL);
    if(!c){
        av_log(NULL,AV_LOG_ERROR,"Failed to copy %s codec parameters to decoder context\n",av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        goto __AVFORMAT;
    }

    //將解碼器的參數進行設置:將輸入流的參數直接拷貝即可-----------------
    ret = avcodec_parameters_to_context(c,st->codecpar);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Cannot initialize the conversion context\n");
        goto __ACCODEC;
    }

    //創建圖片轉換上下文(在上面參數拷貝后面)-----------------
    //源圖像寬、高、像素格式;目標圖像寬、高、像素格式;以及圖像拉伸使用的算法
    img_convert_cxt = sws_getContext(c->width,c->height,c->pix_fmt,
                                    c->width,c->height,AV_PIX_FMT_BGR24,
                                    SWS_BICUBIC,NULL,NULL,NULL);    //后面為源、目的圖像過濾器,和參數
    if(img_convert_cxt==NULL){
        av_log(NULL,AV_LOG_ERROR,"Can`t open the codec\n");
        goto __ACCODEC;
    }

    //打開解碼器-----------------
    ret = avcodec_open2(c,codec,NULL);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t open the codec\n");
        goto __SWSCXT;
    }

    frame = av_frame_alloc();
    if(!frame){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc the frame for video\n");
        goto __SWSCXT;
    }

    //開始讀取數據
    av_init_packet(&packet);    //初始化數據包
    while(av_read_frame(fmt_cxt,&packet)>=0&&(--cnt)>=0){
        if(packet.stream_index == stream_idx){
            if(decode_write_frame(out_filename,c,img_convert_cxt,
                        frame,&frameCnt,&packet,0)<0){
                av_log(NULL,AV_LOG_ERROR,"Failed to decode frame in decode_write_frame\n");
                goto __PACKET;
            }

            av_packet_unref(&packet);    //注意:減少引用不會釋放空間,因為本函數還在引用這個packet結構體;
            //所以我們在解碼函數中剩余的其他幀數據,還是保留在packet中的,后面根據av_read_frame繼續向內部添加數據
        }
    }
    //處理packet中剩余的幀
    packet.data = NULL;
    packet.size = 0;
    decode_write_frame(out_filename,c,img_convert_cxt,
                        frame,&frameCnt,&packet,1);

//下面開始處理在堆上創建的內存空間    
__PACKET:
    av_frame_free(&frame);
    av_packet_unref(&packet);    //減少引用,使得自己釋放空間

__SWSCXT:
    sws_freeContext(img_convert_cxt);

__ACCODEC:
    avcodec_free_context(&c);

__AVFORMAT:
    avformat_close_input(&fmt_cxt);
    return;
}

int main(int argc,char* argv[]){
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);

    if(argc < 3){
        av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2\n");
        return -1;
    }

    decode_video(argv[1],argv[2]);
    return 0;
}
View Code
gcc 02_ffmpeg_dec.c -o fd -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswscale
./fd gfxm.mp4 gfxm

二:H264編碼處理

(一)編碼流程

avcodec_open2在解碼和編碼過程不同(在設置參數上):
  解碼:因為輸入文件中輸入流本身已經設置好了參數,解碼時只需要把這些參數拷貝即可,不需要手動設置
  編碼:需要我們手動設置,比如分辨率...

(二)編碼實戰:(YUV編碼為H264)FFmpeg學習(五)H264結構

#include <stdio.h>
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>

#define V_WIDTH 640
#define V_HEIGHT 480

AVFormatContext* open_dev(){
    char* devicename = "/dev/video0";    //設備文件描述符
    char errors[1024];
    int ret;

    AVFormatContext* fmt_ctx=NULL;    //格式上下文獲取-----av_read_frame獲取packet
    AVDictionary* options=NULL;
    AVInputFormat *iformat=NULL;
    AVPacket packet;    //包結構

    //獲取輸入(采集)格式
    iformat = av_find_input_format("video4linux2");    //驅動,用來錄制視頻
    //設置參數 ffmpeg -f video4linux2 -pixel_format yuyv422 -video_size 640*480  -framerate 15 -i /dev/video0 out.yuv
    av_dict_set(&options,"video_size","640*480",0);
    av_dict_set(&options,"framerate","30",0);
    av_dict_set(&options,"pixel_format","yuyv422",0);

    //打開輸入設備
    ret = avformat_open_input(&fmt_ctx,devicename,iformat,&options);    //----打開輸入設備,初始化格式上下文和選項
    if(ret<0){
        av_strerror(ret,errors,1024);
        av_log(NULL,AV_LOG_ERROR,"Failed to open video device,[%d]%s\n",ret,errors);
        return NULL;
    }
    av_log(NULL,AV_LOG_INFO,"Success to open video device\n");

    return fmt_ctx;
}

//作用:編碼,將yuv420轉H264
AVCodecContext* open_encoder(int width,int height){
    //------1.打開編碼器
    AVCodec* codec = avcodec_find_encoder_by_name("libx264");
    if(!codec){
        av_log(NULL,AV_LOG_ERROR,"Failed to open video encoder\n");
        return NULL;
    }
    //------2.創建上下文
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if(!codec_ctx){
        av_log(NULL,AV_LOG_ERROR,"Failed to open video encoder context\n");
        return NULL;
    }
    //------3.設置上下文參數
    //SPS/PPS
    codec_ctx->profile = FF_PROFILE_H264_HIGH_444;    //main分支最高級別編碼
    codec_ctx->level = 50;                            //表示level級別是5.0;支持最大分辨率2560×1920
    //分辨率
    codec_ctx->width = width;                        //設置分辨率--寬度
    codec_ctx->height = height;                        //設置分辨率--高度
    //GOP
    codec_ctx->gop_size = 250;                        //設置GOP個數,根據業務處理;
    codec_ctx->keyint_min = 25;                        //(option)如果GOP過大,我們就在中間多設置幾個I幀,使得避免卡頓。這里表示在一組GOP中,最小插入I幀的間隔
    //B幀(增加壓縮比,降低碼率)
    codec_ctx->has_b_frames = 1;                    //(option)標志是否允許存在B幀
    codec_ctx->max_b_frames = 3;                    //(option)設置中間連續B幀的最大個數
    //參考幀(越大,還原性越好,但是壓縮慢)
    codec_ctx->refs = 3;                            //(option)設置參考幀最大數量,緩沖隊列
    //要進行編碼的數據的原始數據格式(輸入的原始數據)
    codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;        //注意:我們如果不進行轉換yuyv422為yuv420的話,這里直接設置為AV_PIX_FMT_YUYV422P即可
    //設置碼率
    codec_ctx->bit_rate = 600000;                    //設置平均碼率600kpbs(根據業務)
    //設置幀率
    codec_ctx->time_base = (AVRational){1,25};        //時間基,為幀率的倒數;幀與幀之間的間隔
    codec_ctx->framerate = (AVRational){25,1};        //幀率,每秒25幀

    if(codec->id==AV_CODEC_ID_H264)                 //如果是h264,則可以使用預先設置好的h264參數集,壓縮速度slow慢,保證質量
        av_opt_set(codec_ctx->priv_data,"preset","slow",0);

    //------4.打開編碼器
    if(avcodec_open2(codec_ctx,codec,NULL)<0){
        av_log(NULL,AV_LOG_ERROR,"Failed to open libx264 context\n");
        avcodec_free_context(&codec_ctx);
        return NULL;
    }

    return codec_ctx;
}


static AVFrame* initFrame(int width,int height){
    int ret;
    AVFrame* frame = av_frame_alloc();    //分配frame空間,但是數據真正被存放在buffer中
    if(!frame){
        av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n");
        return NULL;
    }

    //主要是設置分辨率,用來分配空間
    frame->width = width;
    frame->height = height;
    frame->format = AV_PIX_FMT_YUV420P;

    ret = av_frame_get_buffer(frame,32);        //第二個參數是對齊,對於音頻,我們直接設置0,視頻中必須為32位對齊
    if(ret<0){    //內存分配出錯
        av_log(NULL,AV_LOG_ERROR,"Failed to alloc frame buffer\n");
        av_frame_free(&frame);
        return NULL;
    }
    return frame;
}

//開始進行編碼操作
static void encode(AVCodecContext* enc_ctx,AVFrame* frame,AVPacket* newpkt,FILE* encfp){
    int len=0,got_output;

    int ret = avcodec_encode_video2(enc_ctx,newpkt,frame,&got_output);  //傳入上下文,輸出packet,輸入frame,got_output表示是否產生avpacket,不是每一個frame對應一個packet,而是可能存在多個frame對應一個packet
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"avcodec_encode_video2 error! [%d] %s\n",ret,av_err2str(ret));
        return;
    }

    if(got_output){
        len = fwrite(newpkt->data,1,newpkt->size,encfp);
        fflush(encfp);
        if(len!=newpkt->size){
            av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d\n",len,newpkt->size);
        }else{
            av_log(NULL,AV_LOG_INFO,"Success write newpkt to file\n");
        }
    }

    av_packet_unref(newpkt);    //注意:不同於avcodec_receive_packet,我們這里需要手動處理newpkt空間,負責會出現Provided packet is too small問題
}


void rec_video(){
    char errors[1024];
    int ret,count=0,len,i,j,y_idx,u_idx,v_idx,base_h,base=0;

    AVFormatContext* fmt_ctx = NULL;
    AVCodecContext* enc_ctx = NULL;
    AVFrame* fmt = NULL;
    AVPacket packet;

    //打開文件,存放轉換為yuv420的數據
    FILE* fp = fopen("./video.yuv","wb");
    if(fp==NULL){
        av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n");
        goto fail;
    }

    //打開文件,存放編碼后數據(其實上面沒必要存在)
    FILE* encfp = fopen("./video.h264","wb");
    if(encfp==NULL){
        av_log(NULL,AV_LOG_ERROR,"Failed to open out H264 file\n");
        goto fail;
    }


    //打開攝像頭設備的上下文格式
    fmt_ctx = open_dev();
    if(!fmt_ctx)
        goto fail;
    //打開編碼上下文
    enc_ctx = open_encoder(V_WIDTH,V_HEIGHT);
    if(!enc_ctx)
        goto fail;
    //創建AVFrame
    AVFrame* frame = initFrame(V_WIDTH,V_HEIGHT);
    //創建AVPacket
    AVPacket* newpkt = av_packet_alloc();
    if(!newpkt){
        av_log(NULL,AV_LOG_ERROR,"Failed to alloc avpacket\n");
        goto fail;
    }

    //開始從設備中讀取數據
    while((ret=av_read_frame(fmt_ctx,&packet))==0&&count++<100){
        av_log(NULL,AV_LOG_INFO,"Packet size:%d(%p),cout:%d\n",packet.size,packet.data,count);

        //------先將YUYV422數據轉YUV420數據(重點)
        //序列為YU YV YU YV,一個yuv422幀的長度 width * height * 2 個字節
        //丟棄偶數行 u v

        //先存放Y數據
        memset(frame->data[0],0,V_WIDTH*V_HEIGHT*sizeof(char));
        for(i=0,y_idx=0;i<2*V_HEIGHT*V_WIDTH;i+=2){
            frame->data[0][y_idx++]=packet.data[i];
        }
        //再獲取U、V數據
        memset(frame->data[1],0,V_WIDTH*V_HEIGHT*sizeof(char)/4);
        memset(frame->data[2],0,V_WIDTH*V_HEIGHT*sizeof(char)/4);
        for(i=0,u_idx=0,v_idx=0;i<V_HEIGHT;i+=2){    //丟棄偶數行,注意:i<V_HEIGHT*2,總數據量是Y+UV,可以達到V_HEIGHT*2行
            base_h = i*2*V_WIDTH;                    //獲取奇數行開頭數據位置
            for(j=0;j<V_WIDTH*2;j+=4){                //遍歷這一行數據,每隔4個為1組 y u y v
                frame->data[1][u_idx++] = packet.data[base_h+j+1];    //獲取U數據
                frame->data[2][v_idx++] = packet.data[base_h+j+3];    //獲取V數據
            }
        }
        
        //寫入yuv420數據
        fwrite(frame->data[0],1,V_WIDTH*V_HEIGHT,fp);
        fwrite(frame->data[1],1,V_WIDTH*V_HEIGHT/4,fp);
        fwrite(frame->data[2],1,V_WIDTH*V_HEIGHT/4,fp);

        //開始編碼
        frame->pts = base++;    //重點:對幀的pts進行順序累加;不能設置隨機值;H264要求編碼的幀的pts是連續的值
        encode(enc_ctx,frame,newpkt,encfp);

        //釋放空間
        av_packet_unref(&packet);
    }
    encode(enc_ctx,NULL,newpkt,encfp);    //告訴編碼器編碼結束,將后面剩余的數據全部寫入即可
fail:
    if(fp)
        fclose(fp);
    if(encfp)
        fclose(encfp);

    if(frame)
        av_frame_free(&frame);
    if(newpkt)
        av_packet_free(&newpkt);

    //關閉設備、釋放上下文空間
    if(enc_ctx)
        avcodec_free_context(&enc_ctx);
    avformat_close_input(&fmt_ctx);
    return ;
}

int main(int argc,char* argv)
{

    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);
    //注冊所有的設備,包括我們需要的音頻設備
    avdevice_register_all();

    rec_video();
    return 0;
}
View Code
gcc 01_ffmpeg_enc.c -o fe -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice

三:AAC解碼處理

回顧:FFmpeg學習(三)音頻基礎重采樣

解碼方式同上面的H264解碼,不同的是解碼函數:

/*@param avctx編解碼器上下文
 *@param [out] frame用於存儲解碼音頻樣本的AVFrame
 *@param [out] got_frame_ptr如果沒有幀可以解碼則為零,否則為非零
 *@param [in] avpkt包含輸入緩沖區的輸入AVPacket
 *@return 如果在解碼期間發生錯誤,則返回否定錯誤代碼,否則返回從輸入AVPacket消耗的字節數。
 */
int avcodec_decode_audio4 ( AVCodecContext * avctx, AVFrame * frame, int * got_frame_ptr, const AVPacket * avpkt )

(一)獲取AAC數據

FFmpeg學習(二)FFmpeg命令學習

ffmpeg -i out.mp4 -acodec copy -vn out.aac

一個AAC原始幀包含一段時間內1024個采樣及相關數據!!

AAC:

音頻幀的播放時間=一個AAC幀對應的采樣樣本的個數/采樣頻率(單位為s)

一幀 1024個 sample。采樣率 Samplerate 44100Hz,每秒44100個sample, 所以根據公式 音頻幀的播放時間=一個AAC幀對應的采樣樣本的個數/采樣頻率

當前AAC一幀的播放時間是= 1024*1000/44100= 22.32ms(單位為ms)

MP3:

mp3 每幀均為1152個字節, 則:

frame_duration = 1152 * 1000 / sample_rate

例如:sample_rate = 44100HZ時,計算出的時長為26.122ms,這就是經常聽到的mp3每幀播放時間固定為26ms的由來。

(二)編程實戰(AAC解碼為PCM數據)

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>

#define MAX_AUDIO_FRAME_SIZE  192000

SwrContext* getSwrCxt(AVCodecContext* c,uint8_t** dst,int* dst_size,int* ret){
    SwrContext* swr_cxt = NULL;

    //------開始設置解碼轉換參數
    uint64_t out_channel_layout = AV_CH_LAYOUT_STEREO;  //輸入數據的通道布局,是雙聲道
    int out_nb_samples = 1024;                          //采樣個數
    enum AVSampleFormat sample_fmt = AV_SAMPLE_FMT_S16;
    int out_sample_rate = 44100;
    int out_channels = av_get_channel_layout_nb_channels(out_channel_layout);
    *dst_size = av_samples_get_buffer_size(NULL,out_channels,out_nb_samples,sample_fmt,1);    //這是每次采樣的數據 = 通道數×每個通道采樣(每幀)×格式

    *dst = (uint8_t*)av_malloc(MAX_AUDIO_FRAME_SIZE*2);  //最大開辟的空間
    if(*dst==NULL){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc dst memory!\n");
        *ret = -3;
        return NULL;
    }

    int64_t in_channel_layout = av_get_default_channel_layout(c->channels);

    //----------創建重采樣的上下文
    swr_cxt = swr_alloc_set_opts(NULL,                    //設置已經創建好的上下文,如果沒有,則為NULL
                                out_channel_layout,       //設置輸出目標的通道布局(雙聲道,立體聲,...,方位增寬)
                                sample_fmt,               //設置輸出目標的采樣格式,設置為32位浮點型
                                out_sample_rate,          //設置輸出目標的采樣率
                                in_channel_layout,        //輸入數據的通道布局,是雙聲道
                                c->sample_fmt,            //輸入數據的采樣格式為s16le
                                c->sample_rate,           //輸入的采樣率
                                0,                        //日志級別
                                NULL);                    //日志上下文
    if(!swr_cxt){
        av_log(NULL,AV_LOG_ERROR,"Failed to set swr context\n");
        *ret = -1;
        return NULL;
    }

    //----------初始化上下文
    if(swr_init(swr_cxt)<0){
        av_log(NULL,AV_LOG_ERROR,"Failed to initial swr context\n");
        *ret = -2;
        return NULL;
    }
    return swr_cxt;
}

void dec_audio(char* in_filename,char* out_filename){
    int ret,stream_idx,cnt=1;
    int got_aac;
    FILE* fp = NULL;

    AVFormatContext* fmt_ctx=NULL;

    AVCodec* codec = NULL;
    AVCodecContext* c = NULL;
    SwrContext* swr_cxt = NULL;

    AVPacket* packet;    //包結構
    AVFrame* frame;

    uint8_t* dst;
    int dst_size;

    ret = avformat_open_input(&fmt_ctx,in_filename,NULL,NULL);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t open input file:%s\n",in_filename);
        return;
    }

    ret = avformat_find_stream_info(fmt_ctx,0); //獲取輸入流的詳細信息
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t find stream information!\n");
        goto __AVFORMAT;
    }

    //媒體文件句柄 / 流類型 / 請求的流編號(-1則自動去找) / 相關流索引號(比如音頻對應的視頻流索引號),不指定則-1 / 如果非空,則返回所選流的解碼器(指針獲取) / flag當前未定義
    ret = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t find the best stream!\n");
        goto __AVFORMAT;
    }
    stream_idx = ret;

    c = fmt_ctx->streams[stream_idx]->codec;    //獲取輸入流的編解碼上下文
    codec = avcodec_find_decoder(c->codec_id);  //獲取輸入流對應的解碼器

    if(!codec){
        av_log(NULL,AV_LOG_ERROR,"Can`t find the decoder!\n");
        goto __AVFORMAT;
    }

    //打開解碼器
    ret = avcodec_open2(c,codec,NULL);
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Can`t open avcodec!\n");
        goto __AVFORMAT;
    }

    swr_cxt = getSwrCxt(c,&dst,&dst_size,&ret);
    if(!swr_cxt){
        if(ret==-2)
            goto __SWRCXT;
        goto __AVFORMAT;
    }

    packet = av_packet_alloc();
    if(!packet){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc packet!\n");
        goto __SWRCXT;
    }
    av_init_packet(packet);

    frame = av_frame_alloc();
    if(!frame){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc packet!\n");
        goto __PACKET;
    }

    //打開文件
    fp = fopen(out_filename,"wb");
    if(fp==NULL){
        av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n");
        goto __FRAME;
    }

    //開始從設備中讀取數據
    while(av_read_frame(fmt_ctx,packet)>=0){
        if(packet->stream_index == stream_idx){
            av_log(NULL,AV_LOG_INFO,"decode %d aac frame to pcm\n",cnt++);
            //開始解碼aac數據為pcm
            ret = avcodec_decode_audio4(c,frame,&got_aac,packet);
            if(ret<0){
                av_log(NULL,AV_LOG_ERROR,"Failed to open out file\n");
                goto fail;
            }
            //轉碼
            if(got_aac>0){
                swr_convert(swr_cxt,                                 //上下文
                            &dst,                                    //輸出數組(雙指針)
                            MAX_AUDIO_FRAME_SIZE,                    //每個通道的采樣數
                            (const uint8_t**)frame->data,            //輸入數據,來自與packet.data,要改造格式
                            frame->nb_samples);                      //輸入的通道采樣數
                fwrite(dst,1,dst_size,fp);
            }
            got_aac = 0;

        }

        //釋放空間
        av_free_packet(packet);
    }

//----------釋放空間
fail:
    fclose(fp);
__FRAME:
    av_frame_free(&frame);
__PACKET:
    av_free_packet(packet);
__SWRCXT:
    av_freep(&dst);
    swr_free(&swr_cxt);
__AVFORMAT:
    //關閉設備、釋放上下文空間
    avformat_close_input(&fmt_ctx);
    return ;
}

int main(int argc,char* argv[])
{
    av_register_all();
    av_log_set_level(AV_LOG_DEBUG);

    if(argc<=2){
        av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2\n");
        return -1;
    }

    dec_audio(argv[1],argv[2]);
    return 0;
}
View Code
gcc -o fad 04_ffmpeg_aac_dec.c -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswresample
./fad out.aac out.pcm
ffplay out.pcm -ar 44100 -ac 2 -f s16le

四:AAC編碼處理

回顧:FFmpeg學習(三)音頻基礎

(一)編程實戰(PCM轉AAC數據)

參考:FFmpeg音頻編碼 ---- pcm轉aac

https://www.jianshu.com/p/b16fac8e05b6

ffmpeg -i out.aac -ar 48000 -ac 2 -f f32le 48000_2_f32le.pcm
#include <libavutil/log.h>
#include <libavcodec/avcodec.h>

static int check_sample_fmt(const AVCodec* codec,enum AVSampleFormat sample_fmt){
    const enum AVSampleFormat* p = codec->sample_fmts;
    while(*p!=AV_SAMPLE_FMT_NONE){  // 通過AV_SAMPLE_FMT_NONE作為結束符
        av_log(NULL,AV_LOG_INFO,"codec[%s] support sample fromat: %s\n",codec->name,av_get_sample_fmt_name(*p));
        if(*p==sample_fmt)
            return 1;
        p++;
    }
    return 0;
}

static int check_sample_rate(const AVCodec* codec,const int sample_rate){
    const int* p = codec->supported_samplerates;
    while(*p!=0){   // 0作為退出條件,比如libfdk-aacenc.c的aac_sample_rates
        av_log(NULL,AV_LOG_INFO,"codec[%s] support sample rate: %dhz\n",codec->name,*p);
        if(*p==sample_rate)
            return 1;
        p++;
    }
    return 0;
}

static int check_sample_channel_layout(const AVCodec* codec,const uint64_t channel_layout){
    const uint64_t* p = codec->channel_layouts;
    if(!p){         // 不是每個codec都給出支持的channel_layout
        av_log(NULL,AV_LOG_ERROR,"codec[%s] not set channel layout\n",codec->name);
        return 1;
    }
    while(*p!=0){    // 0作為退出條件,比如libfdk-aacenc.c的aac_channel_layout
        av_log(NULL,AV_LOG_INFO,"codec[%s] support channel layout: %d\n",codec->name,*p);
        if(*p==channel_layout)
            return 1;
        p++;
    }
    return 0;
}

static int check_codec(AVCodec* codec,AVCodecContext* codec_ctx){
    //檢查格式
    if(!check_sample_fmt(codec,codec_ctx->sample_fmt)){
        av_log(NULL,AV_LOG_ERROR,"codec[%s] not support sample fromat: %d\n",codec->name,
            av_get_sample_fmt_name(codec_ctx->sample_fmt));
        return 0;
    }
    //檢查比特率
    if(!check_sample_rate(codec,codec_ctx->sample_rate)){
        av_log(NULL,AV_LOG_ERROR,"codec[%s] not support sample rate: %dhz\n",codec->name,codec_ctx->sample_rate);
        return 0;
    }
    //檢查通道布局
    if(!check_sample_channel_layout(codec,codec_ctx->channel_layout)){
        av_log(NULL,AV_LOG_ERROR,"codec[%s] not support channel layout: %d\n",codec->name,codec_ctx->channel_layout);
        return 0;
    }

    //打印所有配置
    av_log(NULL,AV_LOG_INFO,"\n\ncodec[%s] encode config\n",codec->name);
    av_log(NULL,AV_LOG_INFO,"bit_rate:%ldkbps\n",codec_ctx->bit_rate/1024);
    av_log(NULL,AV_LOG_INFO,"sample_rate:%d\n",codec_ctx->sample_rate);
    av_log(NULL,AV_LOG_INFO,"sample_fmt:%s\n",av_get_sample_fmt_name(codec_ctx->sample_fmt));
    av_log(NULL,AV_LOG_INFO,"channels:%d\n",codec_ctx->channels);
    av_log(NULL,AV_LOG_INFO,"aac frame_size:%d\n\n",codec_ctx->frame_size);

    return 1;
}

AVCodecContext* getCodeCtx(int* ret){
    //---------1.打開編碼器
    enum AVCodecID codec_id = AV_CODEC_ID_AAC;
    AVCodec* codec = avcodec_find_encoder(codec_id);
    //AVCodec* codec = avcodec_find_encoder_by_name("libfdk_aac");    //內部要求的采樣大小就是是s16le------重點
    if(!codec){
        av_log(NULL,AV_LOG_ERROR,"Codec not found\n");
        *ret = -1;
        return NULL;
    }
    //---------2.創建上下文
    AVCodecContext* codec_ctx = avcodec_alloc_context3(codec);
    if(!codec_ctx){
        av_log(NULL,AV_LOG_ERROR,"Codec can`t alloc context\n");
        *ret = -1;
        return NULL;
    }
    //------------------設置上下文參數
    codec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;         //設置編碼類型,音頻編碼,還可以設置編碼器id codec_id等等
    codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;         //設置采樣格式---------(該字段是被固定了),上面說到,libfdk_aac處理16位,所以設置為16位。所以我們一般是將其他格式進行重采樣為16位
    codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;    //設置通道布局,雙通道
    codec_ctx->channels = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);                            //設置通道數(其實和上面一樣)
    codec_ctx->sample_rate = 48000;                     //設置采樣率
    codec_ctx->profile = FF_PROFILE_AAC_LOW;            //設置AAC編碼格式,如果設置了這個字段,就不需要設置下面的比特率了
    codec_ctx->bit_rate = 128*1024;                     //設置比特率,128k;對於每個編碼方式,都有最低編碼碼率。AAC_LC:128k AAC HE:64k AAC HE V2:32k

    //---------------檢查是否支持采樣格式信息
    if(!check_codec(codec,codec_ctx)){
        av_log(NULL,AV_LOG_ERROR,"Codec can`t pass check\n");
        *ret = -2;
        return codec_ctx;
    }

    //---------3.打開編碼器
    if(avcodec_open2(codec_ctx,codec,NULL)<0){
        av_log(NULL,AV_LOG_ERROR,"Codec can`t be open\n");
        *ret = -2;
        return codec_ctx;
    }

    //輸出一下aac每次采樣點個數(1024)
    av_log(NULL,AV_LOG_INFO,"aac frame_size:%d\n",codec_ctx->frame_size);   //frame_size---每幀單個通道的采樣點!!!!!!!!!!
    *ret = 0;
    return codec_ctx;
}

AVFrame* initFrame(AVCodecContext* codec_ctx){
    AVFrame* frame = av_frame_alloc();    //分配frame空間,但是數據真正被存放在buffer中
    if(!frame){
        av_log(NULL,AV_LOG_ERROR,"Failed to create frame\n");
        return NULL;
    }

    //配置3要素,才能為buffer分配空間
    frame->nb_samples = codec_ctx->frame_size;          //每幀數據中,單通道采樣數,和前面重采樣配置一樣
    frame->format = codec_ctx->sample_fmt;              //采樣大小
    frame->channel_layout = codec_ctx->channel_layout;  //通道布局
    av_frame_get_buffer(frame,0);        //第二個參數是對齊  
    if(!frame->data[0]){
        av_log(NULL,AV_LOG_ERROR,"Failed to create frame buffer\n");
        return NULL;
    }
    return frame;
}

void f32le_convert_to_fltp(float* f32le,float* fltp,int nb_samples){
    float* fltp_l = fltp;               //左通道
    float* fltp_r = fltp+nb_samples;    //右通道
    for(int i=0;i<nb_samples;i++){
        fltp_l[i] = f32le[i*2];
        fltp_r[i] = f32le[i*2+1];
    }
}

static void get_adts_header(AVCodecContext *ctx, uint8_t *adts_header, int aac_length)
{
    uint8_t freq_idx = 0;    //0: 96000 Hz  3: 48000 Hz 4: 44100 Hz
    switch (ctx->sample_rate) {
        case 96000: freq_idx = 0; break;
        case 88200: freq_idx = 1; break;
        case 64000: freq_idx = 2; break;
        case 48000: freq_idx = 3; break;
        case 44100: freq_idx = 4; break;
        case 32000: freq_idx = 5; break;
        case 24000: freq_idx = 6; break;
        case 22050: freq_idx = 7; break;
        case 16000: freq_idx = 8; break;
        case 12000: freq_idx = 9; break;
        case 11025: freq_idx = 10; break;
        case 8000: freq_idx = 11; break;
        case 7350: freq_idx = 12; break;
        default: freq_idx = 4; break;
    }
    uint8_t chanCfg = ctx->channels;
    uint32_t frame_length = aac_length + 7;
    adts_header[0] = 0xFF;
    adts_header[1] = 0xF1;
    adts_header[2] = ((ctx->profile) << 6) + (freq_idx << 2) + (chanCfg >> 2);
    adts_header[3] = (((chanCfg & 3) << 6) + (frame_length  >> 11));
    adts_header[4] = ((frame_length & 0x7FF) >> 3);
    adts_header[5] = (((frame_length & 7) << 5) + 0x1F);
    adts_header[6] = 0xFC;
}

static int encode(AVCodecContext* codec_ctx,AVFrame* frame,AVPacket* pkt,FILE* out_fp){
    int ret,len;

    //--------進行編碼
    ret = avcodec_send_frame(codec_ctx,frame);    
    if(ret<0){
        av_log(NULL,AV_LOG_ERROR,"Error sending the frame to the encoder\n");
        return -1;
    }

    // 編碼和解碼都是一樣的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOF
    while(ret >= 0){
        ret = avcodec_receive_packet(codec_ctx,pkt);    //獲取編碼后的數據放入packet中
        if(ret<0){
            if(ret==AVERROR(EAGAIN)||ret==AVERROR_EOF){    //讀完數據
                return 0;
            }else{    //編碼器出錯
                av_log(NULL,AV_LOG_ERROR,"avcodec_receive_packet error! [%d] %s\n",ret,av_err2str(ret));
                return -1;
            }
        }

        uint8_t aac_header[7];
        get_adts_header(codec_ctx,aac_header,pkt->size);
        len = fwrite(aac_header,1,7,out_fp);
        fflush(out_fp);
        if(len!=7){
            av_log(NULL,AV_LOG_WARNING,"Warning,ADTS header write error!\n");
        }

        len = fwrite(pkt->data,1,pkt->size,out_fp);
        fflush(out_fp);
        if(len!=pkt->size){
            av_log(NULL,AV_LOG_WARNING,"Warning,newpkt size:%d not equal writen size:%d\n",len,pkt->size);
        }else{
            av_log(NULL,AV_LOG_INFO,"Success write newpkt to file\n");
        }
    }
    return -1;
}

void enc_audio2(char* in_filename,char* out_filename){
    const char* in_pcm_file = in_filename;      // 輸入PCM文件
    const char* out_aac_file = out_filename;     // 輸出的AAC文件
    int ret;
    // 2.分配內存
    AVCodecContext *codec_ctx = getCodeCtx(&ret);

    // 5.打開輸入和輸出文件
    FILE *infile = fopen(in_pcm_file, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", in_pcm_file);
        exit(1);
    }
    FILE *outfile = fopen(out_aac_file, "wb");
    if (!outfile) {
        fprintf(stderr, "Could not open %s\n", out_aac_file);
        exit(1);
    }

    // 6.分配packet
    AVPacket *pkt = av_packet_alloc();
    if (!pkt) {
        fprintf(stderr, "could not allocate the packet\n");
        exit(1);
    }

    // 7.分配frame
    AVFrame *frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate audio frame\n");
        exit(1);
    }
    /* 每次送多少數據給編碼器由:
     *  (1)frame_size(每幀單個通道的采樣點數);
     *  (2)sample_fmt(采樣點格式);
     *  (3)channel_layout(通道布局情況);
     * 3要素決定
     */
    frame->nb_samples     = codec_ctx->frame_size;
    frame->format         = codec_ctx->sample_fmt;
    frame->channel_layout = codec_ctx->channel_layout;
    frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);
    printf("frame nb_samples:%d\n", frame->nb_samples);
    printf("frame sample_fmt:%d\n", frame->format);
    printf("frame channel_layout:%lu\n\n", frame->channel_layout);

    // 8.為frame分配buffer
    ret = av_frame_get_buffer(frame, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not allocate audio data buffers\n");
        exit(1);
    }

    // 9.循環讀取數據
    // 計算出每一幀的數據 單個采樣點的字節 * 通道數目 * 每幀采樣點數量
    int frame_bytes = av_get_bytes_per_sample(frame->format) \
            * frame->channels \
            * frame->nb_samples;
    printf("frame_bytes %d\n", frame_bytes);
    uint8_t *pcm_buf = (uint8_t *)malloc(frame_bytes);
    if(!pcm_buf) {
        printf("pcm_buf malloc failed\n");
        return 1;
    }
    uint8_t *pcm_temp_buf = (uint8_t *)malloc(frame_bytes);
    if(!pcm_temp_buf) {
        printf("pcm_temp_buf malloc failed\n");
        return 1;
    }
    int64_t pts = 0;
    printf("start enode\n");
    for (;;) {
        memset(pcm_buf, 0, frame_bytes);
        size_t read_bytes = fread(pcm_buf, 1, frame_bytes, infile);
        if(read_bytes <= 0) {
            printf("read file finish\n");
            break;
        }

        // 10.確保該frame可寫, 如果編碼器內部保持了內存參考計數,則需要重新拷貝一個備份 目的是新寫入的數據和編碼器保存的數據不能產生沖突
        ret = av_frame_make_writable(frame);
        if(ret != 0)
            printf("av_frame_make_writable failed, ret = %d\n", ret);

        // 11.填充音頻幀
        if(AV_SAMPLE_FMT_S16 == frame->format) {
            // 將讀取到的PCM數據填充到frame去,但要注意格式的匹配, 是planar還是packed都要區分清楚
            ret = av_samples_fill_arrays(frame->data, frame->linesize,
                                   pcm_buf, frame->channels,
                                   frame->nb_samples, frame->format, 0);
        } else {
            // 將讀取到的PCM數據填充到frame去,但要注意格式的匹配, 是planar還是packed都要區分清楚
            // 將本地的f32le packed模式的數據轉為float palanar
            memset(pcm_temp_buf, 0, frame_bytes);
            f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf, frame->nb_samples);
            ret = av_samples_fill_arrays(frame->data, frame->linesize,
                                   pcm_temp_buf, frame->channels,
                                   frame->nb_samples, frame->format, 0);
        }

        // 12.編碼
        pts += frame->nb_samples;
        frame->pts = pts;       // 使用采樣率作為pts的單位,具體換算成秒 pts*1/采樣率
        ret = encode(codec_ctx, frame, pkt, outfile);
        if(ret < 0) {
            printf("encode failed\n");
            break;
        }
    }

    // 13.沖刷編碼器
    encode(codec_ctx, NULL, pkt, outfile);

    // 14.關閉文件
    fclose(infile);
    fclose(outfile);

    // 15.釋放內存
    if(pcm_buf) {
        free(pcm_buf);
    }
    if (pcm_temp_buf) {
        free(pcm_temp_buf);
    }
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&codec_ctx);
    printf("main finish, please enter Enter and exit\n");
    return 0;
}

void enc_audio(char* in_filename,char* out_filename){
    int ret,len;
    int64_t pts = 0;
    int frame_bytes;
    uint8_t* pcm_buf,* pcm_temp_buf; //存放pcm數據
    FILE* in_fp,* out_fp;    

    AVCodecContext* c = NULL;

    AVPacket* pkt = NULL;
    AVFrame* frame = NULL;

    c = getCodeCtx(&ret);
    if(ret<0){
        if(ret==-2)
            goto __CODECCTX;
        return;
    }

    pkt = av_packet_alloc();
    if(!pkt){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for packet\n");
        goto __CODECCTX;
    }

    frame = initFrame(c);
    if(!frame){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for frame\n");
        goto __PACKET;
    }

    //-------開始從pcm文件中讀取數據
    //獲取每一幀的數據大小 !!!!
    //單個采樣點所需字節×通道數×每幀下單通道采樣點數量
    frame_bytes = av_get_bytes_per_sample(frame->format)*frame->channels*frame->nb_samples;
    av_log(NULL,AV_LOG_INFO,"frame_bytes:%d\n",frame_bytes);

    pcm_buf = (uint8_t*)malloc(frame_bytes);
    if(!pcm_buf){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for pcm_buf\n");
        goto __FRAME;
    }

    pcm_temp_buf = (uint8_t*)malloc(frame_bytes);
    if(!pcm_temp_buf){
        av_log(NULL,AV_LOG_ERROR,"Can`t alloc memory for pcm_temp_buf\n");
        goto __PCMBUF;
    }

    in_fp = fopen(in_filename,"rb");
    if(!in_fp){
        av_log(NULL,AV_LOG_ERROR,"Can`t open file:%s\n",in_filename);
        goto __PCMTEMPBUF;
    }

    out_fp = fopen(out_filename,"wb");
    if(!out_fp){
        av_log(NULL,AV_LOG_ERROR,"Can`t open file:%s\n",out_filename);
        goto __INFILE;
    }

    av_log(NULL,AV_LOG_INFO,"\nStart encode\n");
    while(1){
        memset(pcm_buf,0,frame_bytes);  //初始化空間,開始讀取文件
        len = fread(pcm_buf,1,frame_bytes,in_fp);
        if(len<=0){
            av_log(NULL,AV_LOG_WARNING,"read file finsih\n");
            break;
        }

        //----------確保frame可寫, 如果編碼器內部保持了內存參考計數,則需要重新拷貝一個備份 目的是新寫入的數據和編碼器保存的數據不能產生沖突
        ret = av_frame_make_writable(frame);
        if(ret!=0){
            av_log(NULL,AV_LOG_WARNING,"av_frame_make_writable failed, ret = %d\n", ret);

        }

        //----------開始填充音頻幀
         // 將讀取到的PCM數據填充到frame去,但要注意格式的匹配, 是planar還是packed都要區分清楚
         /*
            AV_SAMPLE_FMT_S16格式,也就是兩個聲道交替存儲,每個樣點2個字節。
            而FFmpeg默認的AAC編碼器不支持這種格式的編碼,
            只支持AV_SAMPLE_FMT_FLTP,這種格式是按平面存儲,樣點是float類型,
            所謂平面也就是每個聲道單獨存儲,比如左聲道存儲到data[0]中,右聲道存儲到data[1]中。
        */
        //ffmpeg只能提取packed格式的PCM數據,在編碼時候如果輸入要為fltp則需要進行轉換
        //flt格式:ffmpeg -i buweishui.aac -ar 48000 -ac 2 -f f32le 48000_2_f32le.pcm
        //s16格式:ffmpeg -i buweishui.aac -ar 48000 -ac 2 -f s16le 48000_2_s16le.pcm
        //然而我們設置的frame格式就是AV_SAMPLE_FMT_FLTP格式,所以需要對其進行轉換
        memset(pcm_temp_buf,0,frame_bytes);
        f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf,frame->nb_samples);
        ret = av_samples_fill_arrays(frame->data,frame->linesize,
            pcm_temp_buf,
            frame->channels,frame->nb_samples,frame->format,0);  // 將讀取到的PCM數據填充到frame去,但要注意格式的匹配, 是planar還是packed都要區分清楚
        
        //----------開始編碼
        pts += frame->nb_samples;
        frame->pts = pts;   //重點:對幀的pts進行順序累加;不能設置隨機值;要求編碼的幀的pts是連續的值
        ret = encode(c,frame,pkt,out_fp);
        if(ret<0){
            av_log(NULL,AV_LOG_WARNING,"encode error!\n");
            break;
        }
    }

    //--------沖刷編碼器
    encode(c,NULL,pkt,out_fp);


__FINSIH:
    fclose(out_fp);
__INFILE:
    fclose(in_fp);
__PCMTEMPBUF:
    if(pcm_temp_buf)
        free(pcm_temp_buf);
__PCMBUF:
    if(pcm_buf)
        free(pcm_buf);
__FRAME:
    av_frame_free(&frame);
__PACKET:
    av_packet_free(&pkt);
__CODECCTX:
    avcodec_free_context(&c);
}

int main(int argc, char* argv[])
{
    av_log_set_level(AV_LOG_DEBUG);
    if(argc<=2){
        av_log(NULL,AV_LOG_ERROR,"The Count of Parameter must be than 2\n");
        return -1;
    }

    enc_audio(argv[1],argv[2]);
    return 0;
}
View Code
gcc 03_ffmpeg_aac_enc.c -o fae -I /usr/local/ffmpeg/include/ -L /usr/local/ffmpeg/lib/ -lavutil -lavformat -lavcodec -lavdevice -lswscale -lswresample
./fae 48000_2_f32le.pcm out3.aac

對於格式轉換最好使用swr_convert函數


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM