第4講-FFmepg-視頻解碼

內容一:FFmpeg-命令行補充?

案例:視頻轉為高質量 GIF 動圖?

命令:./ffmpeg -ss 00:00:03 -t 3 -i Test.mov -s 640x360 -r  15 dongtu.gif

解釋:
    1、ffmpeg 是你剛才安裝的程序;

    2、-ss 00:00:03 表示從第 00 分鐘 03 秒開始制作 GIF,如果你想從第 9 秒開始,則輸入 -ss 00:00:09,或者 -ss 9,支持小數點,所以也可以輸入 -ss 00:00:11.3,或者 -ss 34.6 之類的,如果不加該命令,則從 0 秒開始制作;

    3、-t 3 表示把持續(xù) 3 秒的視頻轉換為 GIF,你可以把它改為其他數字,例如 1.5,7 等等,時間越長,GIF 體積越大,如果不加該命令,則把整個視頻轉為 GIF;

    4、-i 表示 invert 的意思,轉換;

    5、Test.mov 就是你要轉換的視頻,名稱最好不要有中文,不要留空格,支持多種視頻格式;

    6、-s 640x360 是 GIF 的分辨率,視頻分辨率可能是 1080p,但你制作的 GIF 可以轉為 720p 等,允許自定義,分辨率越高體積越大,如果不加該命令,則保持分辨率不變;

    7、-r  15 表示幀率,網上下載的視頻幀率通常為 24,設為 15 效果挺好了,幀率越高體積越大,如果不加該命令,則保持幀率不變;

    8、dongtu.gif:就是你要輸出的文件,你也可以把它命名為 hello.gif 等等。

內容二:FFmpeg-視頻解碼?

>內容:將音視頻解碼在Android平臺進行實現(xiàn)
>作業(yè):然后移植到iOS平臺(提示:將iOS環(huán)境搭建好,然后童鞋們填寫代碼)
    功能實現(xiàn)是一模一樣,沒有區(qū)別?
    下一節(jié)課講解?寫好了直接發(fā)到我的郵箱?

第一點:確定音視頻編解碼流程學習?

    第一步:組冊組件
        av_register_all()
        例如:編碼器、解碼器等等…

    第二步:打開封裝格式->打開文件
        例如:.mp4、.mov、.wmv文件等等...
        avformat_open_input();

    第三步:查找視頻流
        如果是視頻解碼,那么查找視頻流,如果是音頻解碼,那么就查找音頻流
        avformat_find_stream_info();

    第四步:查找視頻解碼器
        1、查找視頻流索引位置
        2、根據視頻流索引,獲取解碼器上下文
        3、根據解碼器上下文,獲得解碼器ID,然后查找解碼器

    第五步:打開解碼器
        avcodec_open2();

    第六步:讀取視頻壓縮數據->循環(huán)讀取
        每讀取一幀數據,立馬解碼一幀數據

    第七步:視頻解碼->播放視頻->得到視頻像素數據

    第八步:關閉解碼器->解碼完成

第二點:實現(xiàn)功能->寫代碼

#include <jni.h>
#include <string>
#include <android/log.h>

//特殊處理:在安卓底層開發(fā)中,需要注意的
//因為要支持C/C++混合編程
extern "C" {
//核心庫
#include "libavcodec/avcodec.h"
//封裝格式處理庫
#include "libavformat/avformat.h"
//工具庫
#include "libavutil/imgutils.h"
//視頻像素數據格式庫
#include "libswscale/swscale.h"

//視頻解碼
JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_decodevideo_MainActivity_ffmepgDecodeVideo(JNIEnv *env, jobject jobj,
                                                                         jstring jinFilePath,
                                                                         jstring joutFilePath);

}


JNIEXPORT void JNICALL Java_com_tz_dream_ffmpeg_decodevideo_MainActivity_ffmepgDecodeVideo(
        JNIEnv *env,
        jobject jobj, jstring jinFilePath, jstring joutFilePath) {

    //第一步:組冊組件
    av_register_all();

    //第二步:打開封裝格式->打開文件
    //參數一:封裝格式上下文
    //作用:保存整個視頻信息(解碼器、編碼器等等...)
    //信息:碼率、幀率等...
    AVFormatContext* avformat_context = avformat_alloc_context();
    //參數二:視頻路徑
    const char *url = env->GetStringUTFChars(jinFilePath, NULL);
    //在我們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
        __android_log_print(ANDROID_LOG_INFO, "main", "打開文件失敗");
        //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){
        __android_log_print(ANDROID_LOG_INFO, "main", "查找失敗");
        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){
        __android_log_print(ANDROID_LOG_INFO, "main", "打開解碼器失敗");
        return;
    }

    //測試一下
    //打印信息
    __android_log_print(ANDROID_LOG_INFO, "main", "解碼器名稱:%s", avcodec->name);



    //第六步:讀取視頻壓縮數據->循環(huán)讀取
    //1、分析av_read_frame參數
    //參數一:封裝格式上下文
    //參數二:一幀壓縮數據 = 一張圖片
    //av_read_frame()
    //結構體大小計算:字節(jié)對齊原則
    AVPacket* packet = (AVPacket*)av_malloc(sizeof(AVPacket));

    //3.2 解碼一幀視頻壓縮數據->進行解碼(作用:用于解碼操作)
    //開辟一塊內存空間
    AVFrame* avframe_in = av_frame_alloc();
    int decode_result = 0;


    //4、注意:在這里我們不能夠保證解碼出來的一幀視頻像素數據格式是yuv格式
    //參數一:源文件->原始視頻像素數據格式寬
    //參數二:源文件->原始視頻像素數據格式高
    //參數三:源文件->原始視頻像素數據格式類型
    //參數四:目標文件->目標視頻像素數據格式寬
    //參數五:目標文件->目標視頻像素數據格式高
    //參數六:目標文件->目標視頻像素數據格式類型
    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);

    //創(chuàng)建一個yuv420視頻像素數據格式緩沖區(qū)(一幀數據)
    AVFrame* avframe_yuv420p = av_frame_alloc();
    //給緩沖區(qū)設置類型->yuv420類型
    //得到YUV420P緩沖區(qū)大小
    //參數一:視頻像素數據格式類型->YUV420P格式
    //參數二:一幀視頻像素數據寬 = 視頻寬
    //參數三:一幀視頻像素數據高 = 視頻高
    //參數四:字節(jié)對齊方式->默認是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)
    //參數二:目標->每一行大小
    //參數三:原始數據
    //參數四:目標->格式類型
    //參數五:寬
    //參數六:高
    //參數七:字節(jié)對齊方式
    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 = env->GetStringUTFChars(joutFilePath, NULL);
    FILE* file_yuv420p = fopen(outfile, "wb+");
    if (file_yuv420p == NULL){
        __android_log_print(ANDROID_LOG_INFO, "main", "輸出文件打開失敗");
        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 發(fā)送一幀視頻壓縮數據
            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等等...
                //保證:我的解碼后的視頻像素數據格式統(tǒng)一為yuv420P->通用的格式
                //進行類型轉換: 將解碼出來的視頻像素點數據格式->統(tǒng)一轉類型為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表示:色度
                //有規(guī)律
                //YUV420P格式規(guī)范一:Y結構表示一個像素(一個像素對應一個Y)
                //YUV420P格式規(guī)范二: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++;
                __android_log_print(ANDROID_LOG_INFO, "main", "當前解碼第%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);
}

實例工程

iOS工程 https://pan.baidu.com/s/1dFlhDdB
Android工程 https://pan.baidu.com/s/1jHAzHzw

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 178,741評論 25 709
  • 原文地址:http://blog.csdn.net/yipie/article/details/7912291 摘...
    冬的天閱讀 7,273評論 1 6
  • 教程一:視頻截圖(Tutorial 01: Making Screencaps) 首先我們需要了解視頻文件的一些基...
    90后的思維閱讀 4,980評論 0 3
  • 有人說,浮生若夢,一切人事不過泡沫幻影,轉瞬即逝,唯有那最珍貴的情感,如曇花一現(xiàn),在生命中留下亙久芬芳。我想,當初...
    益星大話游戲閱讀 227評論 0 0
  • 咳嗽吊水第二天,吊瓶上的作業(yè)今夜又要在診所里與手機上完成。 由于下鄉(xiāng)回遲了,挨到這個時候才打針??人栽?..
    幾小幾閱讀 559評論 0 0

友情鏈接更多精彩內容