ffmpeg音視頻編解碼的封裝

//導入頭文件

//核心庫

#include "libavcodec/avcodec.h"

//封裝格式處理庫

#include "libavformat/avformat.h"

//工具庫

#include "libavutil/imgutils.h"


//視頻像素數據格式庫

#include "libswscale/swscale.h"

#include "libswresample/swresample.h"

.h文件的類方法

//視頻解碼

+(void) ffmepgVideoDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath;


//音頻解碼

+(void)ffmpegAudioDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath;


//FFmpeg視頻編碼

+(void)ffmpegVideoEncode:(NSString*)filePath outFilePath:(NSString*)outFilePath;


//音頻編碼

+(void)ffmpegAudioEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath;

.m的實現


//inFilePath:輸入文件->mp4、mov等等->封裝格式

//outFilePath:輸出文件->YUV文件->視頻像素數據格式

+(void) ffmepgVideoDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{

    //第一步:組冊組件

    av_register_all();

    

    //第二步:打開封裝格式->打開文件

    //參數一:封裝格式上下文

    //作用:保存整個視頻信息(解碼器、編碼器等等...)

    //信息:碼率、幀率等...

    AVFormatContext* avformat_context = avformat_alloc_context();

    //參數二:視頻路徑

    const char *url = [inFilePath UTF8String];

    //在我們iOS裏面

    //NSString* path = @"/user/dream/test.mov";

    //const char *url = [path UTF8String]

    //參數三:指定輸入的格式

    //參數四:設置默認參數

    int avformat_open_input_result = avformat_open_input(&avformat_context, url, NULL, NULL);

    if (avformat_open_input_result != 0){

        //安卓平臺下log

        NSLog(@"打開文件失敗");

        //iOS平臺下log

        //NSLog("打開文件失敗");

        //不同的平臺替換不同平臺log日誌

        return;

    }

    

    //第三步:查找視頻流->拿到視頻信息

    //參數一:封裝格式上下文

    //參數二:指定默認配置

    int avformat_find_stream_info_result = avformat_find_stream_info(avformat_context, NULL);

    if (avformat_find_stream_info_result < 0){

        NSLog(@"查找失敗");

        return;

    }

    

    //第四步:查找視頻解碼器

    //1、查找視頻流索引位置

    int av_stream_index = -1;

    for (int i = 0; i < avformat_context->nb_streams; ++i) {

        //判斷流類型:視頻流、音頻流、字母流等等...

        if (avformat_context->streams[i]-> codec->codec_type == AVMEDIA_TYPE_VIDEO){

            av_stream_index = i;

            break;

        }

    }

    

    //2、根據視頻流索引,獲取解碼器上下文

    AVCodecContext *avcodec_context = avformat_context->streams[av_stream_index]-> codec;

    

    //3、根據解碼器上下文,獲得解碼器ID,然後查找解碼器

    AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);

    

    

    //第五步:打開解碼器

    int avcodec_open2_result = avcodec_open2(avcodec_context, avcodec, NULL);

    if (avcodec_open2_result != 0){

        NSLog(@"打開解碼器失敗");

        return;

    }

    

    //測試一下

    //打印信息

    NSLog(@"解碼器名稱:%s", avcodec->name);

    

    

    //第六步:讀取視頻壓縮數據->循環讀取

    //1、分析av_read_frame參數

    //參數一:封裝格式上下文

    //參數二:一幀壓縮數據 = 一張圖片

    //av_read_frame()

    //結構體大小計算:字節對齊原則

    AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));

    

    //3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用於解碼操作)

    //開闢一塊內存空間

    AVFrame* avframe_in = av_frame_alloc();

    int decode_result = 0;

    

    

    //4、注意:在這裏我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式

    //參數一:源文件->原始視頻像素數據格式寬

    //參數二:源文件->原始視頻像素數據格式高

    //參數三:源文件->原始視頻像素數據格式類型

    //參數四:目標文件->目標視頻像素數據格式寬

    //參數五:目標文件->目標視頻像素數據格式高

    //參數六:目標文件->目標視頻像素數據格式類型

    struct SwsContext *swscontext = sws_getContext(avcodec_context->width,

                                                   avcodec_context->height,

                                                   avcodec_context->pix_fmt,

                                                   avcodec_context->width,

                                                   avcodec_context->height,

                                                   AV_PIX_FMT_YUV420P,

                                                   SWS_BICUBIC,

                                                   NULL,

                                                   NULL,

                                                   NULL);

    

    //創建一個yuv420視頻像素數據格式緩衝區(一幀數據)

    AVFrame* avframe_yuv420p = av_frame_alloc();

    //給緩衝區設置類型->yuv420類型

    //得到YUV420P緩衝區大小

    //參數一:視頻像素數據格式類型->YUV420P格式

    //參數二:一幀視頻像素數據寬 = 視頻寬

    //參數三:一幀視頻像素數據高 = 視頻高

    //參數四:字節對齊方式->默認是1

    int buffer_size = av_image_get_buffer_size(AV_PIX_FMT_YUV420P,

                                               avcodec_context->width,

                                               avcodec_context->height,

                                               1);

    

    //開闢一塊內存空間

    uint8_t *out_buffer = (uint8_t *)av_malloc(buffer_size);

    //向avframe_yuv420p->填充數據

    //參數一:目標->填充數據(avframe_yuv420p)

    //參數二:目標->每一行大小

    //參數三:原始數據

    //參數四:目標->格式類型

    //參數五:寬

    //參數六:高

    //參數七:字節對齊方式

    av_image_fill_arrays(avframe_yuv420p->data,

                         avframe_yuv420p->linesize,

                         out_buffer,

                         AV_PIX_FMT_YUV420P,

                         avcodec_context->width,

                         avcodec_context->height,

                         1);

    

    int y_size, u_size, v_size;

    

    

    //5.2 將yuv420p數據寫入.yuv文件中

    //打開寫入文件

    const char *outfile = [outFilePath UTF8String];

    FILE* file_yuv420p = fopen(outfile, "wb+");

    if (file_yuv420p == NULL){

        NSLog(@"輸出文件打開失敗");

        return;

    }

    

    int current_index = 0;

    

    while (av_read_frame(avformat_context, packet) >= 0){

        // >=:讀取到了

        // <0:讀取錯誤或者讀取完畢

        //2、是否是我們的視頻流

        if (packet->stream_index == av_stream_index){

            //第七步:解碼

            //學習一下C基礎,結構體

            //3、解碼一幀壓縮數據->得到視頻像素數據->yuv格式

            //採用新的API

            //3.1 發送一幀視頻壓縮數據

            avcodec_send_packet(avcodec_context, packet);

            //3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用於解碼操作)

            decode_result = avcodec_receive_frame(avcodec_context, avframe_in);

            if (decode_result == 0){

                //解碼成功

                //4、注意:在這裏我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式

                //視頻像素數據格式很多種類型: yuv420P、yuv422p、yuv444p等等...

                //保證:我的解碼後的視頻像素數據格式統一爲yuv420P->通用的格式

                //進行類型轉換: 將解碼出來的視頻像素點數據格式->統一轉類型爲yuv420P

                //sws_scale作用:進行類型轉換的

                //參數一:視頻像素數據格式上下文

                //參數二:原來的視頻像素數據格式->輸入數據

                //參數三:原來的視頻像素數據格式->輸入畫面每一行大小

                //參數四:原來的視頻像素數據格式->輸入畫面每一行開始位置(填寫:0->表示從原點開始讀取)

                //參數五:原來的視頻像素數據格式->輸入數據行數

                //參數六:轉換類型後視頻像素數據格式->輸出數據

                //參數七:轉換類型後視頻像素數據格式->輸出畫面每一行大小

                sws_scale(swscontext,

                          (const uint8_t *const *)avframe_in->data,

                          avframe_in->linesize,

                          0,

                          avcodec_context->height,

                          avframe_yuv420p->data,

                          avframe_yuv420p->linesize);

                

                //方式一:直接顯示視頻上面去

                //方式二:寫入yuv文件格式

                //5、將yuv420p數據寫入.yuv文件中

                //5.1 計算YUV大小

                //分析一下原理?

                //Y表示:亮度

                //UV表示:色度

                //有規律

                //YUV420P格式規範一:Y結構表示一個像素(一個像素對應一個Y)

                //YUV420P格式規範二:4個像素點對應一個(U和V: 4Y = U = V)

                y_size = avcodec_context->width * avcodec_context->height;

                u_size = y_size / 4;

                v_size = y_size / 4;

                //5.2 寫入.yuv文件

                //首先->Y數據

                fwrite(avframe_yuv420p->data[0], 1, y_size, file_yuv420p);

                //其次->U數據

                fwrite(avframe_yuv420p->data[1], 1, u_size, file_yuv420p);

                //再其次->V數據

                fwrite(avframe_yuv420p->data[2], 1, v_size, file_yuv420p);

                

                current_index++;

                NSLog(@"當前解碼第%d幀", current_index);

            }

        }

    }

    

    //第八步:釋放內存資源,關閉解碼器

    av_packet_free(&packet);

    fclose(file_yuv420p);

    av_frame_free(&avframe_in);

    av_frame_free(&avframe_yuv420p);

    free(out_buffer);

    avcodec_close(avcodec_context);

    avformat_free_context(avformat_context);

}

//音頻解碼

+(void)ffmpegAudioDecode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{

    //第一步:組冊組件->解碼器、編碼器等等…

    //視頻解碼器、視頻編碼器、音頻解碼器、音頻編碼器等等…

    av_register_all();

    

    //第二步:打開封裝格式文件(解封裝)

    //參數一:封裝格式上下文

    AVFormatContext *avformat_context = avformat_alloc_context();

    //參數二:視頻路徑

    const char *cinFilePath = [inFilePath UTF8String];

    //參數三:指定輸入的格式

    //參數四:設置默認參數

    if (avformat_open_input(&avformat_context, cinFilePath, NULL, NULL) != 0) {

        NSLog(@"打開文件失敗");

        return;

    }

    

    //第三步:查找音頻流(視頻流、字母流等…)信息

    if (avformat_find_stream_info(avformat_context, NULL) < 0) {

        NSLog(@"查找失敗");

        return;

    }

    

    //第四步:查找音頻解碼器

    //1、查找音頻流索引位置

    int av_audio_stream_index = -1;

    for (int i = 0; i < avformat_context->nb_streams; ++i) {

        //判斷流類型:視頻流、音頻流、字母流等等...

        if (avformat_context->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO){

            av_audio_stream_index = i;

            break;

        }

    }

    //2、根據視頻流索引,獲取解碼器上下文

    AVCodecContext *avcodec_context = avformat_context->streams[av_audio_stream_index]->codec;

    //3、根據音頻解碼器上下文,獲得解碼器ID,然後查找音頻解碼器

    AVCodec *avcodec = avcodec_find_decoder(avcodec_context->codec_id);

    

    //第五步:打開音頻解碼器

    if (avcodec_open2(avcodec_context, avcodec, NULL) != 0) {

        NSLog(@"打開解碼器失敗");

        return;

    }

    //打印信息

    NSLog(@"解碼器名稱:%s", avcodec->name);

    

    //第六步:循環讀取每一幀音頻壓縮數據

    //參數一:封裝格式上下文呢

    //參數二:音頻壓縮數據(一幀)

    //返回值:>=0表示讀取成功,<0表示失敗或者解碼完成(讀取完畢)

    //準備一幀音頻壓縮數據

    AVPacket *avPacket = (AVPacket *) av_malloc(sizeof(AVPacket));

    //準備一幀音頻採樣數據

    AVFrame *avFrame = av_frame_alloc();

    

    //3、類型轉換->統一轉換爲pcm格式->swr_convert()

    //初始化音頻採樣數據上下文

    //3.1:開闢一塊內存空間

    SwrContext *swrContext = swr_alloc();

    //3.2:設置默認配置

    //參數一:音頻採樣數據上下文

    //參數二:輸出聲道佈局(立體聲、環繞聲...)

    //參數三:輸出採樣精度(編碼)

    //參數四:輸出採樣率

    //參數五:輸入聲道佈局

    int64_t in_ch_layout = av_get_default_channel_layout(avcodec_context->channels);

    //參數六:輸入採樣精度

    //參數七:輸入採樣率

    //參數八:日誌統計開始位置

    //參數九:日誌上下文

    swr_alloc_set_opts(swrContext,

                       AV_CH_LAYOUT_STEREO,

                       AV_SAMPLE_FMT_S16,

                       avcodec_context->sample_rate,

                       in_ch_layout,

                       avcodec_context->sample_fmt,

                       avcodec_context->sample_rate,

                       0,

                       NULL);

    

    //3.3:初始化上下文

    swr_init(swrContext);

    

    //3.4:統一輸出音頻採樣數據格式->pcm

    int MAX_AUDIO_SIZE = 44100 * 2;

    uint8_t *out_buffer = (uint8_t *) av_malloc(MAX_AUDIO_SIZE);

    

    //4、獲取緩衝區實際大小

    int out_nb_channels = av_get_channel_layout_nb_channels(AV_CH_LAYOUT_STEREO);

    

    //5.1 打開文件

    const char *outfile = [outFilePath UTF8String];

    FILE* file_pcm = fopen(outfile, "wb+");

    if (file_pcm == NULL){

        NSLog(@"輸出文件打開失敗");

        return;

    }

    

    int current_index = 0;

    

    while (av_read_frame(avformat_context, avPacket) >= 0) {

        //判定這一幀數據是否音頻流(視頻流、音頻流、字母流等等...)

        //1、音頻解碼->判定流類型

        if (avPacket->stream_index == av_audio_stream_index) {

            //音頻流->處理

            //2、音頻解碼->開始解碼

            //2.1 發送數據包->一幀音頻壓縮數據->acc格式、mp3格式

            avcodec_send_packet(avcodec_context, avPacket);

            //2.2 解碼數據包->解碼->一幀音頻採樣數據->pcm格式

            int ret = avcodec_receive_frame(avcodec_context, avFrame);

            if (ret == 0) {

                //表示解碼成功,否則失敗

                //3、類型轉換->統一轉換爲pcm格式->swr_convert()

                //爲什麼呢?因爲解碼之後的音頻採樣數據格式->有很多種類型->保證格式一致

                //參數一:音頻採樣數據上下文

                //參數二:輸出音頻採樣數據

                //參數三:輸出音頻採樣數據大小

                //參數四:輸入音頻採樣數據

                //參數五:輸入音頻採樣數據大小

                swr_convert(swrContext,

                            &out_buffer,

                            MAX_AUDIO_SIZE,

                            (const uint8_t **) avFrame->data,

                            avFrame->nb_samples);

                

                //4、獲取緩衝區實際大小

                //參數一:行大小

                //參數二:輸出聲道數量(單聲道、雙聲道)

                //參數三:輸入大小

                //參數四:輸出音頻採樣數據格式

                //參數五:字節對齊方式->默認是1

                int buffer_size = av_samples_get_buffer_size(NULL,

                                                             out_nb_channels,

                                                             avFrame->nb_samples,

                                                             avcodec_context->sample_fmt,

                                                             1);

                

                //5、寫入文件

                //5.1 打開文件

                //5.2 寫入文件

                fwrite(out_buffer, 1, buffer_size, file_pcm);

                current_index++;

                NSLog(@"當前解碼到了第%d幀", current_index);

            }

        }

    }

    

    //第八步:釋放資源(內存)->關閉解碼器

    av_packet_free(&avPacket);

    fclose(file_pcm);

    av_frame_free(&avFrame);

    free(out_buffer);

    avcodec_close(avcodec_context);

    avformat_free_context(avformat_context);


}


+(void)ffmpegVideoEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{

    //第一步:註冊組件->編碼器、解碼器等等…

    av_register_all();

    

    //第二步:初始化封裝格式上下文->視頻編碼->處理爲視頻壓縮數據格式

    AVFormatContext *avformat_context = avformat_alloc_context();

    //注意事項:FFmepg程序推測輸出文件類型->視頻壓縮數據格式類型

    const char *coutFilePath = [outFilePath UTF8String];

    //得到視頻壓縮數據格式類型(h264、h265、mpeg2等等...)

    AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);

    //指定類型

    avformat_context->oformat = avoutput_format;

    

    //第三步:打開輸出文件

    //參數一:輸出流

    //參數二:輸出文件

    //參數三:權限->輸出到文件中

    if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {

        NSLog(@"打開輸出文件失敗");

        return;

    }

    

    //第四步:創建輸出碼流->創建了一塊內存空間->並不知道他是什麼類型流->希望他是視頻流

    AVStream *av_video_stream = avformat_new_stream(avformat_context, NULL);

    

    //第五步:查找視頻編碼器

    //1、獲取編碼器上下文

    AVCodecContext *avcodec_context = av_video_stream->codec;

    

    //2、設置編解碼器上下文參數->必需設置->不可少

    //目標:設置爲是一個視頻編碼器上下文->指定的是視頻編碼器

    //上下文種類:視頻解碼器、視頻編碼器、音頻解碼器、音頻編碼器

    //2.1 設置視頻編碼器ID

    avcodec_context->codec_id = avoutput_format->video_codec;

    //2.2 設置編碼器類型->視頻編碼器

    //視頻編碼器->AVMEDIA_TYPE_VIDEO

    //音頻編碼器->AVMEDIA_TYPE_AUDIO

    avcodec_context->codec_type = AVMEDIA_TYPE_VIDEO;

    //2.3 設置讀取像素數據格式->編碼的是像素數據格式->視頻像素數據格式->YUV420P(YUV422P、YUV444P等等...)

    //注意:這個類型是根據你解碼的時候指定的解碼的視頻像素數據格式類型

    avcodec_context->pix_fmt = AV_PIX_FMT_YUV420P;

    //2.4 設置視頻寬高->視頻尺寸

    avcodec_context->width = 640;

    avcodec_context->height = 352;

    //2.5 設置幀率->表示每秒25幀

    //視頻信息->幀率 : 25.000 fps

    //f表示:幀數

    //ps表示:時間(單位:每秒)

    avcodec_context->time_base.num = 1;

    avcodec_context->time_base.den = 25;

    //2.6 設置碼率

    //2.6.1 什麼是碼率?

    //含義:每秒傳送的比特(bit)數單位爲 bps(Bit Per Second),比特率越高,傳送數據速度越快。

    //單位:bps,"b"表示數據量,"ps"表示每秒

    //目的:視頻處理->視頻碼率

    //2.6.2 什麼是視頻碼率?

    //含義:視頻碼率就是數據傳輸時單位時間傳送的數據位數,一般我們用的單位是kbps即千位每秒

    //視頻碼率計算如下?

    //基本的算法是:【碼率】(kbps)=【視頻大小 - 音頻大小】(bit位) /【時間】(秒)

    //例如:Test.mov時間 = 24,文件大小(視頻+音頻) = 1.73MB

    //視頻大小 = 1.34MB(文件佔比:77%) = 1.34MB * 1024 * 1024 * 8 = 字節大小 = 468365字節 = 468Kbps

    //音頻大小 = 376KB(文件佔比:21%)

    //計算出來值->碼率 : 468Kbps->表示1000,b表示位(bit->位)

    //總結:碼率越大,視頻越大

    avcodec_context->bit_rate = 468000;

    

    //2.7 設置GOP->影響到視頻質量問題->畫面組->一組連續畫面

    //MPEG格式畫面類型:3種類型->分爲->I幀、P幀、B幀

    //I幀->內部編碼幀->原始幀(原始視頻數據)

    //    完整畫面->關鍵幀(必需的有,如果沒有I,那麼你無法進行編碼,解碼)

    //    視頻第1幀->視頻序列中的第一個幀始終都是I幀,因爲它是關鍵幀

    //P幀->向前預測幀->預測前面的一幀類型,處理數據(前面->I幀、B幀)

    //    P幀數據->根據前面的一幀數據->進行處理->得到了P幀

    //B幀->前後預測幀(雙向預測幀)->前面一幀和後面一幀

    //    B幀壓縮率高,但是對解碼性能要求較高。

    //總結:I只需要考慮自己 = 1幀,P幀考慮自己+前面一幀 = 2幀,B幀考慮自己+前後幀 = 3幀

    //    說白了->P幀和B幀是對I幀壓縮

    //每250幀,插入1個I幀,I幀越少,視頻越小->默認值->視頻不一樣

    avcodec_context->gop_size = 250;

    

    //2.8 設置量化參數->數學算法(高級算法)->不講解了

    //總結:量化係數越小,視頻越是清晰

    //一般情況下都是默認值,最小量化係數默認值是10,最大量化係數默認值是51

    avcodec_context->qmin = 10;

    avcodec_context->qmax = 51;

    

    //2.9 設置b幀最大值->設置不需要B幀

    avcodec_context->max_b_frames = 0;

    

    //第二點:查找編碼器->h264

    //找不到編碼器->h264

    //重要原因是因爲:編譯庫沒有依賴x264庫(默認情況下FFmpeg沒有編譯進行h264庫)

    //第一步:編譯h264庫

    AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);

    if (avcodec == NULL) {

        NSLog(@"找不到編碼器");

        return;

    }

    

    NSLog(@"編碼器名稱爲:%s", avcodec->name);

    

    

    //第六步:打開h264編碼器

    //缺少優化步驟?

    //編碼延時問題

    //編碼選項->編碼設置

    AVDictionary *param = 0;

    if (avcodec_context->codec_id == AV_CODEC_ID_H264) {

        //需要查看x264源碼->x264.c文件

        //第一個值:預備參數

        //key: preset

        //value: slow->慢

        //value: superfast->超快

        av_dict_set(&param, "preset", "slow", 0);

        //第二個值:調優

        //key: tune->調優

        //value: zerolatency->零延遲

        av_dict_set(&param, "tune", "zerolatency", 0);

    }

    if (avcodec_open2(avcodec_context, avcodec, &param) < 0) {

        NSLog(@"打開編碼器失敗");

        return;

    }

    

    //第七步:寫入文件頭信息

    avformat_write_header(avformat_context, NULL);

    

    //第8步:循環編碼yuv文件->視頻像素數據(yuv格式)->編碼->視頻壓縮數據(h264格式)

    //8.1 定義一個緩衝區

    //作用:緩存一幀視頻像素數據

    //8.1.1 獲取緩衝區大小

    int buffer_size = av_image_get_buffer_size(avcodec_context->pix_fmt,

                                               avcodec_context->width,

                                               avcodec_context->height,

                                               1);

    

    //8.1.2 創建一個緩衝區

    int y_size = avcodec_context->width * avcodec_context->height;

    uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);

    

    //8.1.3 打開輸入文件

    const char *cinFilePath = [inFilePath UTF8String];

    FILE *in_file = fopen(cinFilePath, "rb");

    if (in_file == NULL) {

        NSLog(@"文件不存在");

        return;

    }

    

    //8.2.1 開闢一塊內存空間->av_frame_alloc

    //開闢了一塊內存空間

    AVFrame *av_frame = av_frame_alloc();

    //8.2.2 設置緩衝區和AVFrame類型保持一直->填充數據

    av_image_fill_arrays(av_frame->data,

                         av_frame->linesize,

                         out_buffer,

                         avcodec_context->pix_fmt,

                         avcodec_context->width,

                         avcodec_context->height,

                         1);

    

    int i = 0;

    

    //9.2 接收一幀視頻像素數據->編碼爲->視頻壓縮數據格式

    AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);

    int result = 0;

    int current_frame_index = 1;

    while (true) {

        //8.1 從yuv文件裏面讀取緩衝區

        //讀取大小:y_size * 3 / 2

        if (fread(out_buffer, 1, y_size * 3 / 2, in_file) <= 0) {

            NSLog(@"讀取完畢...");

            break;

        } else if (feof(in_file)) {

            break;

        }

        

        //8.2 將緩衝區數據->轉成AVFrame類型

        //給AVFrame填充數據

        //8.2.3 void * restrict->->轉成->AVFrame->ffmpeg數據類型

        //Y值

        av_frame->data[0] = out_buffer;

        //U值

        av_frame->data[1] = out_buffer + y_size;

        //V值

        av_frame->data[2] = out_buffer + y_size * 5 / 4;

        av_frame->pts = i;

        //注意時間戳

        i++;

        //總結:這樣一來我們的AVFrame就有數據了

        

        //第9步:視頻編碼處理

        //9.1 發送一幀視頻像素數據

        avcodec_send_frame(avcodec_context, av_frame);

        //9.2 接收一幀視頻像素數據->編碼爲->視頻壓縮數據格式

        result = avcodec_receive_packet(avcodec_context, av_packet);

        //9.3 判定是否編碼成功

        if (result == 0) {

            //編碼成功

            //第10步:將視頻壓縮數據->寫入到輸出文件中->outFilePath

            av_packet->stream_index = av_video_stream->index;

            result = av_write_frame(avformat_context, av_packet);

            NSLog(@"當前是第%d幀", current_frame_index);

            current_frame_index++;

            //是否輸出成功

            if (result < 0) {

                NSLog(@"輸出一幀數據失敗");

                return;

            }

        }

    }

    

    //第11步:寫入剩餘幀數據->可能沒有

    flush_encoder(avformat_context, 0);

    

    //第12步:寫入文件尾部信息

    av_write_trailer(avformat_context);

    

    //第13步:釋放內存

    avcodec_close(avcodec_context);

    av_free(av_frame);

    av_free(out_buffer);

    av_packet_free(&av_packet);

    avio_close(avformat_context->pb);

    avformat_free_context(avformat_context);

    fclose(in_file);

}

+(void)ffmpegAudioEncode:(NSString*)inFilePath outFilePath:(NSString*)outFilePath{

    //第一步:註冊組件->音頻編碼器等等…

    av_register_all();

    

    //第二步:初始化封裝格式上下文->視頻編碼->處理爲音頻壓縮數據格式

    AVFormatContext *avformat_context = avformat_alloc_context();

    //注意事項:FFmepg程序推測輸出文件類型->音頻壓縮數據格式類型->aac格式

    const char *coutFilePath = [outFilePath UTF8String];

    //得到音頻壓縮數據格式類型(aac、mp3等...)

    AVOutputFormat *avoutput_format = av_guess_format(NULL, coutFilePath, NULL);

    //指定類型

    avformat_context->oformat = avoutput_format;

    

    //第三步:打開輸出文件

    //參數一:輸出流

    //參數二:輸出文件

    //參數三:權限->輸出到文件中

    if (avio_open(&avformat_context->pb, coutFilePath, AVIO_FLAG_WRITE) < 0) {

        NSLog(@"打開輸出文件失敗");

        return;

    }

    

    //第四步:創建輸出碼流->創建了一塊內存空間->並不知道他是什麼類型流->希望他是視頻流

    AVStream *audio_st = avformat_new_stream(avformat_context, NULL);

    

    //第五步:查找音頻編碼器

    //1、獲取編碼器上下文

    AVCodecContext *avcodec_context = audio_st->codec;

    

    //2、設置編解碼器上下文參數->必需設置->不可少

    //目標:設置爲是一個音頻編碼器上下文->指定的是音頻編碼器

    //上下文種類:音頻解碼器、音頻編碼器

    //2.1 設置音頻編碼器ID

    avcodec_context->codec_id = avoutput_format->audio_codec;

    //2.2 設置編碼器類型->音頻編碼器

    //視頻編碼器->AVMEDIA_TYPE_VIDEO

    //音頻編碼器->AVMEDIA_TYPE_AUDIO

    avcodec_context->codec_type = AVMEDIA_TYPE_AUDIO;

    //2.3 設置讀取音頻採樣數據格式->編碼的是音頻採樣數據格式->音頻採樣數據格式->pcm格式

    //注意:這個類型是根據你解碼的時候指定的解碼的音頻採樣數據格式類型

    avcodec_context->sample_fmt = AV_SAMPLE_FMT_S16;

    //設置採樣率

    avcodec_context->sample_rate = 44100;

    //立體聲

    avcodec_context->channel_layout = AV_CH_LAYOUT_STEREO;

    //聲道數量

    int channels = av_get_channel_layout_nb_channels(avcodec_context->channel_layout);

    avcodec_context->channels = channels;

    //設置碼率

    //基本的算法是:【碼率】(kbps)=【視頻大小 - 音頻大小】(bit位) /【時間】(秒)

    avcodec_context->bit_rate = 128000;

    

    //第二點:查找音頻編碼器->aac

    //    AVCodec *avcodec = avcodec_find_encoder(avcodec_context->codec_id);

    AVCodec *avcodec = avcodec_find_encoder_by_name("libfdk_aac");

    if (avcodec == NULL) {

        NSLog(@"找不到音頻編碼器");

        return;

    }

    

    

    //第六步:打開aac編碼器

    if (avcodec_open2(avcodec_context, avcodec, NULL) < 0) {

        NSLog(@"打開音頻編碼器失敗");

        return;

    }

    

    //第七步:寫文件頭(對於某些沒有文件頭的封裝格式,不需要此函數。比如說MPEG2TS)

    avformat_write_header(avformat_context, NULL);

    

    //打開YUV文件

    const char *c_inFilePath = [inFilePath UTF8String];

    FILE *in_file = fopen(c_inFilePath, "rb");

    if (in_file == NULL) {

        NSLog(@"YUV文件打開失敗");

        return;

    }

    

    //第十步:初始化音頻採樣數據幀緩衝區

    AVFrame *av_frame = av_frame_alloc();

    av_frame->nb_samples = avcodec_context->frame_size;

    av_frame->format = avcodec_context->sample_fmt;

    

    //得到音頻採樣數據緩衝區大小

    int buffer_size = av_samples_get_buffer_size(NULL,

                                                 avcodec_context->channels,

                                                 avcodec_context->frame_size,

                                                 avcodec_context->sample_fmt,

                                                 1);

    

    

    //創建緩衝區->存儲音頻採樣數據->一幀數據

    uint8_t *out_buffer = (uint8_t *) av_malloc(buffer_size);

    avcodec_fill_audio_frame(av_frame,

                             avcodec_context->channels,

                             avcodec_context->sample_fmt,

                             (const uint8_t *)out_buffer,

                             buffer_size,

                             1);

    

    //第十二步:創建音頻壓縮數據->幀緩存空間

    AVPacket *av_packet = (AVPacket *) av_malloc(buffer_size);

    

    

    //第十三步:循環讀取視頻像素數據格式->編碼壓縮->視頻壓縮數據格式

    int frame_current = 1;

    int i = 0, ret = 0;

    

    //第八步:循環編碼每一幀視頻

    //即將AVFrame(存儲YUV像素數據)編碼爲AVPacket(存儲H.264等格式的碼流數據)

    while (true) {

        //1、讀取一幀音頻採樣數據

        if (fread(out_buffer, 1, buffer_size, in_file) <= 0) {

            NSLog(@"Failed to read raw data! \n");

            break;

        } else if (feof(in_file)) {

            break;

        }

        

        //2、設置音頻採樣數據格式

        //將outbuffer->av_frame格式

        av_frame->data[0] = out_buffer;

        av_frame->pts = i;

        i++;

        

        //3、編碼一幀音頻採樣數據->得到音頻壓縮數據->aac

        //採用新的API

        //3.1 發送一幀音頻採樣數據

        ret = avcodec_send_frame(avcodec_context, av_frame);

        if (ret != 0) {

            NSLog(@"Failed to send frame! \n");

            return;

        }

        //3.2 編碼一幀音頻採樣數據

        ret = avcodec_receive_packet(avcodec_context, av_packet);

        

        if (ret == 0) {

            //第九步:將編碼後的音頻碼流寫入文件

            NSLog(@"當前編碼到了第%d幀", frame_current);

            frame_current++;

            av_packet->stream_index = audio_st->index;

            ret = av_write_frame(avformat_context, av_packet);

            if (ret < 0) {

                NSLog(@"寫入失敗! \n");

                return;

            }

        } else {

            NSLog(@"Failed to encode! \n");

            return;

        }

    }

    

    //第十步:輸入的像素數據讀取完成後調用此函數。用於輸出編碼器中剩餘的AVPacket。

    ret = flush_encoder(avformat_context, 0);

    if (ret < 0) {

        NSLog(@"Flushing encoder failed\n");

        return;

    }

    

    //第十一步:寫文件尾(對於某些沒有文件頭的封裝格式,不需要此函數。比如說MPEG2TS)

    av_write_trailer(avformat_context);

    

    

    //第十二步:釋放內存,關閉編碼器

    avcodec_close(avcodec_context);

    av_free(av_frame);

    av_free(out_buffer);

    av_packet_free(&av_packet);

    avio_close(avformat_context->pb);

    avformat_free_context(avformat_context);

    fclose(in_file);

}






發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章