FFmpeg開(kāi)發(fā)——基礎(chǔ)篇(二)

前言

書(shū)接上回,我們比較詳細(xì)的介紹了ffmpeg開(kāi)發(fā)過(guò)程中會(huì)接觸到的主要結(jié)構(gòu)體狗唉,當(dāng)然,其實(shí)還有AVFilter模塊涡真,但是對(duì)于初學(xué)者而言分俯,忽略掉過(guò)濾器部分也無(wú)傷大雅,并不影響對(duì)于ffmpeg開(kāi)發(fā)流程的主體的學(xué)習(xí)哆料,而且AVFilter也不算是特別常用缸剪,在音視頻開(kāi)發(fā)中也有其他方式可以實(shí)現(xiàn)AVFilter的效果,因此暫時(shí)可以先忽略东亦。

本文我們用一段相對(duì)完整杏节,但是不算復(fù)雜的ffmpeg程序來(lái)實(shí)現(xiàn)我們上文提到的那些知識(shí)。

環(huán)境準(zhǔn)備

在進(jìn)行ffmpeg開(kāi)發(fā)之前典阵,一般建議大家自行獲得ffmpeg源碼奋渔,手動(dòng)編譯獲得相應(yīng)的動(dòng)態(tài)庫(kù)(dll/so),然后再正式進(jìn)行c/c++開(kāi)發(fā)工作。

ffmpeg可以在windows,linux系統(tǒng)上開(kāi)發(fā)壮啊,一般推薦linux上來(lái)開(kāi)發(fā)(本人用的linux環(huán)境嫉鲸,但也有windows環(huán)境),因?yàn)閣indows其實(shí)也是模擬了一些linux的環(huán)境的歹啼。

windows環(huán)境安裝與編譯

windows環(huán)境下主要參考這篇文章ffmpeg庫(kù)編譯安裝及入門(mén)指南玄渗。

注意以下幾點(diǎn):

  • 博文中作者的建議安裝選項(xiàng)大家都盡可能安裝上。
  • ffmpeg源碼盡可能下載最新版本狸眼。
  • 編譯ffmpeg庫(kù)的build-ffmpeg.sh腳本替換如下
#!/bin/sh
basepath=$(cd `dirname $0`;pwd)
echo ${basepath}

cd ${basepath}/ffmpeg-5.1.2-src
pwd

export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:/d/repos/ffmpeg/x264_install/lib/pkgconfig
echo ${PKG_CONFIG_PATH}

./configure --prefix=${basepath}/ffmpeg_5.2.1_install \
--enable-debug  --disable-asm --disable-stripping --disable-optimizations \
--enable-gpl --enable-libx264 --disable-static --enable-shared \
--extra-cflags=-l${basepath}/x264_install/include --extra-ldflags=-L${basepath}/x264_install/lib

make -j8
make install

主要是添加一些可debug配置藤树,為后面調(diào)試做準(zhǔn)備

linux環(huán)境安裝與編譯

在linux中就不需要安裝MSYS2了,而缺的編譯工具什么的按照提示使用linux的軟件包管理管理工具(比如apt等)安裝即可份企。

然后下載最新源碼也榄,libx264源碼巡莹,編譯過(guò)程仍然可以使用或者 ffmpeg庫(kù)編譯安裝及入門(mén)指南中提供的編譯腳本司志。

注意build-ffmpeg.sh腳本同樣需要替換一下腳本:

#!/bin/sh
basepath=$(cd `dirname $0`;pwd)
echo ${basepath}

cd ${basepath}/ffmpeg-5.1.2-src
pwd

export PKG_CONFIG_PATH=${PKG_CONFIG_PATH}:/d/repos/ffmpeg/x264_install/lib/pkgconfig
echo ${PKG_CONFIG_PATH}

./configure  \
--enable-debug  --disable-asm --disable-stripping --disable-optimizations \
--enable-gpl --enable-libx264 --disable-static --enable-shared \
--extra-cflags=-l${basepath}/x264_install/include --extra-ldflags=-L${basepath}/x264_install/lib

make -j8
make install

去掉了--prefix=xxx配置甜紫,把ffmpeg生成物推送到系統(tǒng)默認(rèn)的環(huán)境變量的路徑中,免得還需要自行配置骂远,后面就可以直接調(diào)用和使用依賴庫(kù)了囚霸。如果想自定義產(chǎn)物生成目錄也可直接參考windows的腳本。

代碼編輯器

代碼編輯器可以使用 Visual Studio Code,Clion,或者其他趁手的都行激才。

生成產(chǎn)物與開(kāi)發(fā)使用

編譯成功之后拓型,不僅有ffmpeg依賴庫(kù)(lib文件夾)和頭文件(include文件夾),還有ffmpeg,ffprobe,ffplayer這樣的可執(zhí)行程序瘸恼,可以直接在命令行中進(jìn)行調(diào)用劣挫。

在后面的開(kāi)發(fā)過(guò)程中,我們至少會(huì)用到頭文件和依賴庫(kù)东帅。

對(duì)于windows環(huán)境而言压固,為了簡(jiǎn)單起見(jiàn),每新建一個(gè)工程靠闭,可以把ffmpeg生成的頭文件都添加進(jìn)來(lái)帐我,然后按需調(diào)(雖然有些不環(huán)保)。

image.png

windows環(huán)境在編譯時(shí)需要指定鏈接庫(kù)愧膀,還是可以參考 ffmpeg庫(kù)編譯安裝及入門(mén)指南拦键;linux如果編譯產(chǎn)物在系統(tǒng)默認(rèn)目錄中的話則不需要。

ffmpeg開(kāi)發(fā)

環(huán)境安裝完畢之后檩淋,正式進(jìn)入正題芬为。

我們要開(kāi)發(fā)的程序的功能是,讀取一個(gè)視頻文件狼钮,解碼音頻和視頻部分碳柱,并且把解碼后的視頻中的一幀或者幾幀圖保存成ppm格式。

這里主要包含到ffmpeg的解封裝熬芜,解碼莲镣,色彩空間轉(zhuǎn)換的過(guò)程,以及對(duì)解碼數(shù)據(jù)的認(rèn)識(shí)涎拉。

至于ppm瑞侮,它是一個(gè)未壓縮的RGB圖片的格式(jpg就是壓縮后的圖片格式),文件在操作系統(tǒng)中可以正常打開(kāi)查看鼓拧,這不是本文的重點(diǎn)半火。

函數(shù)入口

接下來(lái)我們直接看代碼

#include <cstdio>
#include "common.h"
#include "iostream"
// 因?yàn)閒fmpeg中的庫(kù)都是C編寫(xiě)的,使用cpp開(kāi)發(fā)季俩,引用C庫(kù)需要extern "C"配置钮糖,適配C/cpp函數(shù)名編譯的不同規(guī)則
extern "C"{
    #include "libavformat/avformat.h"
    #include "libavformat/avio.h"
    #include "libavcodec/avcodec.h"
    #include "libswscale/swscale.h"
    #include "libavutil//imgutils.h"

}
using  namespace std;

AVFormatContext *av_fmt_ctx_input = nullptr;

int video_stm_index = -1;
int audio_stm_index = -1;
int ret = 0;

// 提前定義好結(jié)構(gòu)體,便于解碼音頻和視頻時(shí)的變量的統(tǒng)一管理
typedef struct StreamContext{
    //解碼音頻的解碼器上下文
    AVCodecContext *audioAVCodecCtx = nullptr;
    //解碼視頻的解碼器上下文
    AVCodecContext *videoAVCodecCtx= nullptr;
    //表示視頻的數(shù)據(jù)流
    AVStream *videoStream= nullptr;
    //表示音頻的數(shù)據(jù)流
    AVStream *audioStream= nullptr;
    //色彩空間轉(zhuǎn)換后的AVFrame
    AVFrame *rgbFrame = nullptr;
};
// 根據(jù)定義好的結(jié)構(gòu)體聲明一個(gè)變量
struct StreamContext streamContext;
// 色彩空間轉(zhuǎn)換模塊的上下文
SwsContext *swsContext = nullptr;

/********其他函數(shù)***********/
//.....
// 后文補(bǔ)充
//......
/********其他函數(shù)***********/


// 入口函數(shù)
int main(int argc,char *args[]) {
    // 同目錄下存放任意一個(gè)MP4文件,便于直接讀取
    const char *input_file = "bunny.mp4";
    // avformat_open_input店归,解封裝阎抒,并讀取文件頭信息,創(chuàng)建av_fmt_ctx_input結(jié)構(gòu)體對(duì)象
    if ((ret = avformat_open_input(&av_fmt_ctx_input,input_file, nullptr, nullptr))<0){
        print_log("avformat_open_input", ret); // 錯(cuò)誤處理消痛,print_log是自定義的一個(gè)函數(shù)且叁,用于打印一些錯(cuò)誤信息
        return -1;
    }
    // 主要針對(duì)某些沒(méi)有文件頭的視頻文件情況,會(huì)嘗試從文件主體中去讀取一些文件的信息
    ret = avformat_find_stream_info(av_fmt_ctx_input, nullptr);
    if(ret<0){
        print_log("avformat_find_stream_info", ret);
        return ret;
    }
    // 打印一下av_fmt_ctx_input目前持有的信息秩伞,(如果不想要也可以去掉)
    av_dump_format(av_fmt_ctx_input,-1,input_file,0);
    // 1逞带,分別對(duì)視頻和音頻的解碼進(jìn)行初始化的準(zhǔn)備
    // 就是獲取對(duì)應(yīng)的流,以及初始化對(duì)應(yīng)的解碼器
    if (initVideo() < 0 || initAudio() < 0){
        return -1;
    }
    // 初始化這個(gè)用來(lái)轉(zhuǎn)換的AVFrame,
    // 需要手動(dòng)設(shè)置frame->data,frame->linesize這兩個(gè)空間 在前一篇文章中說(shuō)到過(guò)
    ret = initRGBFrame();
    if (ret<0){
        print_log("initRGBFrame",-1);
        return ret;
    }
    // 創(chuàng)建AVPakcet結(jié)構(gòu)體的對(duì)象纱新,前一篇文章說(shuō)過(guò)它是存放編碼數(shù)據(jù)的結(jié)構(gòu)體
    AVPacket  *av_packet = av_packet_alloc();
    
    // av_read_frame 讀取視頻文件的中的數(shù)據(jù)流 到av_packet中展氓,
    // 此時(shí)av_packet中就存放了一塊編碼過(guò)的數(shù)據(jù)
    while (av_read_frame(av_fmt_ctx_input,av_packet)>=0){
        // av_packet->stream_index表示這個(gè)packet數(shù)據(jù)來(lái)自AVFormatContext中的streams數(shù)組的哪個(gè)下標(biāo)
        // 通過(guò)判斷來(lái)區(qū)分packet里面裝的是音頻數(shù)據(jù)還是視頻數(shù)據(jù),需要分開(kāi)解碼
        if (av_packet->stream_index == video_stm_index){ // video_stm_index就是我們找到的視頻流所在的數(shù)組下標(biāo)
            ret = decodeData(av_packet,streamContext.videoAVCodecCtx,1);

        }else if (av_packet->stream_index == audio_stm_index){
            // decode audio

        }

        if (ret<0){

            break;
        }

    }
    // 集中釋放AVCodecContext脸爱,AVPacket,AVFormatContext等資源
    avcodec_free_context(&(streamContext.audioAVCodecCtx));
    avcodec_free_context(&(streamContext.videoAVCodecCtx));
    av_packet_free(&av_packet);
    avformat_close_input(&av_fmt_ctx_input);

    return 0;
}

上面是程序的變量和入口函數(shù)带饱,也就是整個(gè)程序的主框架了。

從上面的注釋可以比較通暢的了解程序的執(zhí)行過(guò)程阅羹。從中也能找到前一篇文章中提到的許多代碼片段勺疼,這里其實(shí)算是做了一個(gè)整合。

音視頻配置初始化

接下來(lái)我們看看initVideo和initAudio,其實(shí)兩者基本是一致的捏鱼,理論上可以合并成一個(gè)函數(shù)执庐。


int initVideo(){
    //av_find_best_stream 用于從av_fmt_ctx_input中找到類(lèi)型為AVMEDIA_TYPE_VIDEO的流的數(shù)組下標(biāo)
    // 當(dāng)然由于我們此時(shí)已經(jīng)直到AVFormatContext->nb_streams 流數(shù)組的長(zhǎng)度,所以可以手動(dòng)遍歷导梆。
    // av_find_best_stream函數(shù)就是手動(dòng)遍歷查找的轨淌。
    video_stm_index = av_find_best_stream(av_fmt_ctx_input,AVMEDIA_TYPE_VIDEO,-1,-1, nullptr,0);
    if (video_stm_index == -1 ){ // 如果-1,表示沒(méi)有找到我們想要的數(shù)組下標(biāo),返回錯(cuò)誤
        print_log("video_index_error",video_stm_index); 
        return -1;
    }
    cout<< "video stream index:  "<<video_stm_index<<endl; //打印信息
    //拿到了視頻流
    streamContext.videoStream = av_fmt_ctx_input->streams[video_stm_index];
    
    // 接著開(kāi)始準(zhǔn)備進(jìn)行解碼器的初始化
    // 上一篇文章我們說(shuō)過(guò)看尼,視頻流中有解碼該流的數(shù)據(jù)的解碼器id
    // 此時(shí)我們通過(guò)解碼器id递鹉,找到對(duì)應(yīng)的解碼器的詳細(xì)信息(AVCodec),或者也可以直接把它理解為解碼器
    // avcodec_find_decoder是找對(duì)應(yīng)的解碼器藏斩,avcodec_find_encoder是找對(duì)應(yīng)的編碼器躏结,別弄錯(cuò)了
    auto codec = avcodec_find_decoder(streamContext.videoStream->codecpar->codec_id);
    // 然后通過(guò)這個(gè)codec,創(chuàng)建該解碼器的上下文狰域,
    // 但是此時(shí)上下文里還沒(méi)有視頻流的有效信息
    auto av_codec_ctx = avcodec_alloc_context3(codec);
    // 于是我們把視頻流的有效信息賦值到解碼器上下文中
    ret = avcodec_parameters_to_context(av_codec_ctx,streamContext.videoStream->codecpar);
    if (ret<0){
        print_log("video avcodec_parameters_to_context",ret);
        return ret;
    }
    // 對(duì)解碼器進(jìn)行初始化媳拴,準(zhǔn)備開(kāi)始解碼
    ret = avcodec_open2(av_codec_ctx,codec, nullptr);
    if (ret<0){
        print_log("video avcodec_open2",ret);
        return ret;
    }
    streamContext.videoAVCodecCtx = av_codec_ctx;
    return 0;
}


int initAudio(){
    audio_stm_index = av_find_best_stream(av_fmt_ctx_input,AVMEDIA_TYPE_AUDIO,-1,-1, nullptr,0);
    if (audio_stm_index == -1){
        print_log("audio_index_error",audio_stm_index);
        return -1;
    }
    cout<< "audio stream index "<<audio_stm_index<<endl;
    streamContext.audioStream = av_fmt_ctx_input->streams[audio_stm_index];

    auto codec = avcodec_find_decoder(streamContext.audioStream->codecpar->codec_id);
    auto av_codec_ctx = avcodec_alloc_context3(codec);
    ret = avcodec_parameters_to_context(av_codec_ctx,streamContext.audioStream->codecpar);
    if (ret<0){
        print_log("audio avcodec_parameters_to_context",ret);
        return ret;
    }
    ret = avcodec_open2(av_codec_ctx,codec, nullptr);
    if (ret<0){
        print_log("audio avcodec_open2",ret);
        return ret;
    }
    streamContext.audioAVCodecCtx = av_codec_ctx;

    return 0;
}

根據(jù)上面的代碼和注釋?zhuān)材馨l(fā)現(xiàn),關(guān)于AVStream,AVCodec,AVCodecContext的使用基本都符合前一篇文章中對(duì)于對(duì)應(yīng)結(jié)構(gòu)體的基本使用說(shuō)明兆览。當(dāng)然這個(gè)過(guò)程中是有許多詳細(xì)的參數(shù)是可以設(shè)置的屈溉,也可以把他們變得復(fù)雜一點(diǎn),但是目前這不是重點(diǎn)抬探。

手動(dòng)配置AVFrame->data

接下來(lái)我們看看initRGBFrame的邏輯子巾。

int initRGBFrame(){
    //先創(chuàng)建一個(gè)AVFrame結(jié)構(gòu)體
    streamContext.rgbFrame = av_frame_alloc();
    
    auto width = streamContext.videoAVCodecCtx->width;
    auto height = streamContext.videoAVCodecCtx->height;
    
    // 通過(guò)像素格式,圖片寬高,來(lái)計(jì)算當(dāng)前所需的緩沖空間大小线梗,最后一個(gè)字段是對(duì)齊字?jǐn)?shù)
    auto bufferSize = av_image_get_buffer_size(AV_PIX_FMT_RGB24,width,height,1);
    uint8_t *  buffer = (uint8_t *)av_malloc(bufferSize);
    // AV_PIX_FMT_RGB24  packed RGB 8:8:8, 24bpp, BGRBGR...
    // 在data[8]數(shù)組中保存在data[0]中
    //根據(jù)緩沖大小匿醒,像素格式,寬高來(lái)填充 rgbFrame->data和rgbFrame->linesize
    av_image_fill_arrays(streamContext.rgbFrame->data,streamContext.rgbFrame->linesize,buffer,
                         AV_PIX_FMT_RGB24,width,height,1);
    // 創(chuàng)建視頻幀轉(zhuǎn)換的上下文缠导,libswscale可以提供顏色轉(zhuǎn)換,圖片尺寸放縮等能力
    swsContext = sws_getContext(width,height,streamContext.videoAVCodecCtx->pix_fmt,width,height,
                                AV_PIX_FMT_RGB24,0, nullptr, nullptr, nullptr);
    if (swsContext == nullptr){
        return -1;
    }
    return 0;
}

手動(dòng)創(chuàng)建并填充AVFrame的過(guò)程溉痢,需要首先創(chuàng)建AVFrame的結(jié)構(gòu)體僻造,然后申請(qǐng)?zhí)畛?rgbFrame->data和rgbFrame->linesize這兩個(gè)字段,前一篇文章中說(shuō)到過(guò)孩饼,不是編解碼過(guò)程中使用AVFrame需要我們手動(dòng)申請(qǐng)這塊內(nèi)存髓削。具體可以看FFmpeg開(kāi)發(fā)——基礎(chǔ)篇————AVFrame

現(xiàn)在我們準(zhǔn)備先把視頻解碼成YUV幀镀娶,然后把YUV幀通過(guò)libswscale轉(zhuǎn)換成RGB幀立膛。解碼過(guò)程中使用AVFrame是不需要我們手動(dòng)申請(qǐng)或填充data等字段的,但是scale轉(zhuǎn)換過(guò)程自然就需要了梯码。

解碼與轉(zhuǎn)換

做完了上述的準(zhǔn)備之后宝泵,可以正式開(kāi)始進(jìn)行解碼操作了:從數(shù)據(jù)流中讀取數(shù)據(jù)到AVPacket中,然后把AVPakcet中的數(shù)據(jù)發(fā)送給解碼器轩娶,接著從解碼器中讀取數(shù)據(jù)到AVFrame中儿奶,就獲得了一個(gè)解碼后的幀。

int decodeData(AVPacket  *av_packet,AVCodecContext *av_codec_ctx,int is_video) {
    // 發(fā)送數(shù)據(jù)到解碼器
    ret = avcodec_send_packet(av_codec_ctx,av_packet);
    if (ret<0){
        print_log("video avcodec_send_packet",ret);
        return ret;
    }
    // 創(chuàng)建一個(gè)AVFrame用來(lái)承接解碼后的數(shù)據(jù)(此時(shí)不用在手動(dòng)填充data等字段了)
    AVFrame  *av_frame = av_frame_alloc();
    while (true){
        // 從解碼器中讀取解碼后的數(shù)據(jù)到AVFrame中
        ret = avcodec_receive_frame(av_codec_ctx,av_frame);
        if (ret == AVERROR_EOF){ // 到文件結(jié)束
            ret = 0;
            break;
        } else if (ret == AVERROR(EAGAIN)){ // avpacket的數(shù)據(jù)不夠形成一幀數(shù)據(jù)鳄抒,需要繼續(xù)往解碼器發(fā)送avpacket
            ret = 0;
            break;
        }else if(ret<0){ // 其他錯(cuò)誤
            print_log("video decode error",ret);

            break;
        }else{
            // ret>=0 表示正常,此時(shí)會(huì)得到的av_frame基本上都是YUV420P的色彩格式闯捎,
            if(is_video>0){ //  處理視頻數(shù)據(jù)
                // sws_scale函數(shù)可以對(duì)AVFrame進(jìn)行轉(zhuǎn)換(顏色空間轉(zhuǎn)換,圖片寬高放縮等)
                // (YUV420P) to (packed RGB 8:8:8)
                ret = sws_scale(swsContext, ( uint8_t const* const*)av_frame->data, av_frame->linesize, 0, av_frame->height,
                                streamContext.rgbFrame->data, streamContext.rgbFrame->linesize);
                if (ret<0){
                    print_log("sws_scale_frame",ret);
                    break;
                }
                // 此時(shí)rgbFrame內(nèi)就保存了RGB格式的數(shù)據(jù)许溅,接下來(lái)我們只要把數(shù)據(jù)寫(xiě)入到文件即可
                saveRGBImage(0);
                ret = -1;
                break; // 只解碼一幀就退出
            }else{

            }


        }
    }
    av_frame_free(&av_frame);

    return ret;
}

這里主要涉及到兩個(gè)點(diǎn)瓤鼻,解碼過(guò)程和轉(zhuǎn)換過(guò)程。

解碼過(guò)程的API調(diào)用比較簡(jiǎn)單贤重,也可以看AVFrame之編解碼使用方式茬祷。

轉(zhuǎn)換過(guò)程本質(zhì)上是YUV2RGB的算法以及數(shù)據(jù)存儲(chǔ)方式,關(guān)于前者其實(shí)在移動(dòng)開(kāi)發(fā)中關(guān)于視頻的一些基本概念——YUV與RGB的轉(zhuǎn)換介紹了相關(guān)轉(zhuǎn)換原理并蝗;而數(shù)據(jù)存儲(chǔ)方式則在文章FFmpeg開(kāi)發(fā)——基礎(chǔ)篇(一)之 AVFrame的data與linesize中有介紹到Planar和packed兩種存儲(chǔ)放在在AVFrame->data中的表現(xiàn)形式牲迫。了解不同的存儲(chǔ)方式在ffmpeg中的表現(xiàn)形式我們才能正確的保存數(shù)據(jù)。

保存文件

然后我們最后看看數(shù)據(jù)保存過(guò)程

void saveRGBImage(int index){
    char fileName[32];

    sprintf(fileName,"frame_%d.ppm",index); // 定義一下文件名frame_0.ppm
    FILE  *file = fopen(fileName,"wb"); // 打開(kāi)文件
    if (file == nullptr){
        return;
    }
    int width = streamContext.videoAVCodecCtx->width;
    int height = streamContext.videoAVCodecCtx->height;
    int line_size = streamContext.rgbFrame->linesize[0];
    // 寫(xiě)入ppm文件的文件頭借卧,P6
    fprintf(file, "P6\n%d %d\n255\n", width, height);
    for (int i = 0; i < height; ++i) { 
        //相當(dāng)于一行一行的寫(xiě)入數(shù)據(jù)盹憎,(也可以計(jì)算數(shù)據(jù)總數(shù),一次性寫(xiě)入)
        // line_size是一行的長(zhǎng)度铐刘,從第0行開(kāi)始陪每,每次寫(xiě)入一行長(zhǎng)度的數(shù)據(jù)
        fwrite(streamContext.rgbFrame->data[0]+i*line_size,1,line_size,file);
    }
   fclose(file);
}

ppm格式的詳細(xì)信息見(jiàn)PPM文件格式詳解

log打印的函數(shù)

char* print_log(const char *tag,int ret){
    const int max_buf = 1024;
    char buf_log[2048] = "";
    // av_strerror函數(shù)能夠根據(jù)當(dāng)前錯(cuò)誤碼給我們返回一些錯(cuò)誤信息
    // 雖然非常粗糙,但是聊勝于無(wú)。
    av_strerror(ret,buf_log,max_buf);
    cout<< tag << " error:   %d  %s" << ret << buf_log << endl;
    return "";
}

總結(jié)

把上述代碼合并之后檩禾,就是這個(gè)程序的完整代碼挂签。

我們可以從一個(gè)視頻文件中讀取數(shù)據(jù),解碼盼产,然后獲取其中第一幀YUV幀饵婆,轉(zhuǎn)換為RGB幀,最后把RGB幀保存為一張未壓縮的圖片文件戏售。

雖然我們對(duì)音頻的解碼做了初始化準(zhǔn)備配置侨核,本來(lái)想做些其他功能,后來(lái)感覺(jué)有點(diǎn)多余灌灾,demo中處理視頻就行了搓译,它和video的解碼過(guò)程是一致的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锋喜,一起剝皮案震驚了整個(gè)濱河市些己,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘿般,老刑警劉巖段标,帶你破解...
    沈念sama閱讀 219,188評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異炉奴,居然都是意外死亡怀樟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門(mén)盆佣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)往堡,“玉大人,你說(shuō)我怎么就攤上這事共耍÷腔遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 165,562評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵痹兜,是天一觀的道長(zhǎng)穆咐。 經(jīng)常有香客問(wèn)我,道長(zhǎng)字旭,這世上最難降的妖魔是什么对湃? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,893評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮遗淳,結(jié)果婚禮上拍柒,老公的妹妹穿的比我還像新娘。我一直安慰自己屈暗,他們只是感情好拆讯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,917評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布脂男。 她就那樣靜靜地躺著,像睡著了一般种呐。 火紅的嫁衣襯著肌膚如雪宰翅。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,708評(píng)論 1 305
  • 那天爽室,我揣著相機(jī)與錄音汁讼,去河邊找鬼。 笑死阔墩,一個(gè)胖子當(dāng)著我的面吹牛嘿架,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戈擒,決...
    沈念sama閱讀 40,430評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼艰毒!你這毒婦竟也來(lái)了筐高?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,342評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤丑瞧,失蹤者是張志新(化名)和其女友劉穎柑土,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體绊汹,經(jīng)...
    沈念sama閱讀 45,801評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡稽屏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,976評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了西乖。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片狐榔。...
    茶點(diǎn)故事閱讀 40,115評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖获雕,靈堂內(nèi)的尸體忽然破棺而出薄腻,到底是詐尸還是另有隱情,我是刑警寧澤届案,帶...
    沈念sama閱讀 35,804評(píng)論 5 346
  • 正文 年R本政府宣布庵楷,位于F島的核電站,受9級(jí)特大地震影響楣颠,放射性物質(zhì)發(fā)生泄漏尽纽。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,458評(píng)論 3 331
  • 文/蒙蒙 一童漩、第九天 我趴在偏房一處隱蔽的房頂上張望弄贿。 院中可真熱鬧,春花似錦矫膨、人聲如沸挎春。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,008評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)直奋。三九已至能庆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間脚线,已是汗流浹背搁胆。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,135評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留邮绿,地道東北人渠旁。 一個(gè)月前我還...
    沈念sama閱讀 48,365評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像船逮,于是被迫代替她去往敵國(guó)和親顾腊。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,055評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容