FFmpeg-視頻解碼-YUV數(shù)據(jù)

視頻解碼過程

視頻解碼過程

?般解出來的是YUV420p

FFmpeg視頻解碼流程

FFmpeg視頻解碼流程

關(guān)鍵函數(shù)

關(guān)鍵函數(shù)說明:

  • avcodec_find_decoder:根據(jù)指定的AVCodecID查找注冊的解碼器。
  • av_parser_init:初始化AVCodecParserContext。
  • avcodec_alloc_context3:為AVCodecContext分配內(nèi)存。
  • avcodec_open2:打開解碼器。
  • av_parser_parse2:解析獲得?個Packet。
  • avcodec_send_packet:將AVPacket壓縮數(shù)據(jù)給解碼器。
  • avcodec_receive_frame:獲取到解碼后的AVFrame數(shù)據(jù)。
  • av_get_bytes_per_sample: 獲取每個sample中的字節(jié)數(shù)。

avcodec編解碼API介紹

avcodec_send_packet、avcodec_receive_frame的API是FFmpeg3版本加?的。為了正確

的使?它們,有必要閱讀FFmpeg的?檔說明(請點(diǎn)擊鏈接)。

FFmpeg提供了兩組函數(shù),分別?于編碼和解碼:

  • 解碼:avcodec_send_packet()、avcodec_receive_frame()。
  • 解碼:avcodec_send_frame()、avcodec_receive_packet()。

API的設(shè)計與編解碼的流程?常貼切

建議的使?流程如下:

  1. 像以前?樣設(shè)置并打開AVCodecContext。

  2. 輸?有效的數(shù)據(jù):

    解碼:調(diào)?avcodec_send_packet()給解碼器傳?包含原始的壓縮數(shù)據(jù)的AVPacket對象。

    編碼:調(diào)? avcodec_send_frame()給編碼器傳?包含解壓數(shù)據(jù)的AVFrame對象。

    兩種情況下推薦AVPacket和AVFrame都使?refcounted(引?計數(shù))的模式,否則libavcodec可能不得不對輸?的數(shù)據(jù)進(jìn)?拷?。

  3. 在?個循環(huán)體內(nèi)去接收codec的輸出,即周期性地調(diào)?avcodec_receive_*()來接收codec輸出的數(shù)據(jù):

    解碼:調(diào)?avcodec_receive_frame(),如果成功會返回?個包含未壓縮數(shù)據(jù)的AVFrame。

    編碼:調(diào)?avcodec_receive_packet(),如果成功會返回?個包含壓縮數(shù)據(jù)的AVPacket。

    反復(fù)地調(diào)?avcodec_receive_packet()直到返回 AVERROR(EAGAIN)或其他錯誤。返回 AVERROR(EAGAIN)錯誤表示codec需要新的輸?來輸出更多的數(shù)據(jù)。對于每個輸?的packet或frame,codec?般會輸出?個frame或packet,但是也有可能輸出0個或者多于1個

  4. 流處理結(jié)束的時候需要flush(沖刷) codec。因?yàn)閏odec可能在內(nèi)部緩沖多個frame或 packet,出于性能或其他必要的情況(如考慮B幀的情況)。 處理流程如下:

    調(diào)?avcodec_send_*()傳?的AVFrame或AVPacket指針設(shè)置為NULL。 這將進(jìn)? draining mode(排?模式)。

    反復(fù)地調(diào)?avcodec_receive_*()直到返回AVERROR_EOF,該?法在draining mode 時不會返回AVERROR(EAGAIN)的錯誤,除?你沒有進(jìn)?draining mode。

    當(dāng)重新開啟codec時,需要先調(diào)? avcodec_flush_buffers()來重置codec。

說明:

  1. 編碼或者解碼剛開始的時候,codec可能接收了多個輸?的frame或packet后還沒有輸出 數(shù)據(jù),直到內(nèi)部的buffer被填充滿。上?的使?流程可以處理這種情況。
  2. 理論上,只有在輸出數(shù)據(jù)沒有被完全接收的情況調(diào)?avcodec_send_*()的時候才可能會發(fā) ?AVERROR(EAGAIN)的錯誤。你可以依賴這個機(jī)制來實(shí)現(xiàn)區(qū)別于上?建議流程的處理? 式,?如每次循環(huán)都調(diào)?avcodec_send_*(),在出現(xiàn)AVERROR(EAGAIN)錯誤的時候再去調(diào)?avcodec_receive_*()
  3. 并不是所有的codec都遵循?個嚴(yán)格、可預(yù)測的數(shù)據(jù)處理流程,唯?可以保證的是調(diào)? avcodec_send_()/avcodec_receive_()返回AVERROR(EAGAIN)的時候去 avcodec_receive_()/avcodec_send_()會成功,否則不應(yīng)該返回AVERROR(EAGAIN) 的錯誤。**?般來說,任何codec都不允許?限制地緩存輸?或者輸出。
  4. 在同?個AVCodecContext上混合使?新舊API是不允許的,這將導(dǎo)致未定義的?為。

avcodec_send_packet

函數(shù)int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt); 作?:?持將裸流數(shù)據(jù)包送給解碼器

警告

  • 輸?的avpkt-data緩沖區(qū)必須?于AV_INPUT_PADDING_SIZE,因?yàn)閮?yōu)化的字節(jié)流讀取 器必須?次讀取32或者64?特的數(shù)據(jù)
  • 不能跟之前的API(例如avcodec_decode_video2)混?,否則會返回不可預(yù)知的錯誤

備注

  • 在將包發(fā)送給解碼器的時候,AVCodecContext必須已經(jīng)通過avcodec_open2打開

參數(shù)

  • avctx:解碼上下?
  • avpkt:輸?AVPakcet.通常情況下,輸?數(shù)據(jù)是?個單?的視頻幀或者?個完整的?頻幀。調(diào)?者保留包的原有屬性,解碼器不會修改包的內(nèi)容。解碼器可能創(chuàng)建對包的引?。 如果包沒有引?計數(shù)將拷??份。跟以往的API不?樣,輸?的包的數(shù)據(jù)將被完全地消耗, 如果包含有多個幀,要求多次調(diào)?avcodec_recvive_frame,直到avcodec_recvive_frame返回VERROR(EAGAIN)AVERROR_EOF。輸?參數(shù)可以為 NULL,或者AVPacket的data域設(shè)置為NULL或者size域設(shè)置為0,表示將刷新所有的包,意味著數(shù)據(jù)流已經(jīng)結(jié)束了。第?次發(fā)送刷新會總會成功,第?次發(fā)送刷新包是沒有必要的,并且返回AVERROR_EOF,如果×××緩存了?些幀,返回?個刷新包,將會返回所有的解碼包

返回值

  • 0: 表示成功
  • AVERROR(EAGAIN):當(dāng)前狀態(tài)不接受輸?,?戶必須先使?avcodec_receive_frame() 讀 取數(shù)據(jù)幀;
  • AVERROR_EOF:解碼器已刷新,不能再向其發(fā)送新包;
  • AVERROR(EINVAL):沒有打開解碼器,或者這是?個編碼器,或者要求刷新;
  • AVERRO(ENOMEN):?法將數(shù)據(jù)包添加到內(nèi)部隊(duì)列。

avcodec_receive_frame

函數(shù)int avcodec_receive_frame ( AVCodecContext * avctx, AVFrame * frame )

作?:從解碼器返回已解碼的輸出數(shù)據(jù)。

參數(shù)

  • avctx: 編解碼器上下?
  • frame: 獲取使?reference-counted機(jī)制的audio或者video幀(取決于解碼器類型)。請注意,在執(zhí)?其他操作之前,函數(shù)內(nèi)部將始終先調(diào)?av_frame_unref(frame)。

返回值

  • 0: 成功,返回?個幀
  • AVERROR(EAGAIN): 該狀態(tài)下沒有幀輸出,需要使?avcodec_send_packet發(fā)送新的packet到解碼器
  • AVERROR_EOF: 解碼器已經(jīng)被完全刷新,不再有輸出幀
  • AVERROR(EINVAL): 編解碼器沒打開
  • 其他<0的值: 具體查看對應(yīng)的錯誤碼

H264解碼YUV實(shí)戰(zhàn)

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <libavutil/frame.h>
#include <libavutil/mem.h>

#include <libavcodec/avcodec.h>

#define VIDEO_INBUF_SIZE 20480
#define VIDEO_REFILL_THRESH 4096

static char err_buf[128] = {0};

static char *av_get_err(int errnum) {
    av_strerror(errnum, err_buf, 128);
    return err_buf;
}

static void print_video_format(const AVFrame *frame) {
    printf("width: %u\n", frame->width);
    printf("height: %u\n", frame->height);
    printf("format: %u\n", frame->format);// 格式需要注意
}

static void decode(AVCodecContext *dec_ctx, AVPacket *pkt, AVFrame *frame,
                   FILE *outfile) {
    int ret;
    /* send the packet with the compressed data to the decoder */
    ret = avcodec_send_packet(dec_ctx, pkt);
    if (ret == AVERROR(EAGAIN)) {
        fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
    } else if (ret < 0) {
        fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
                av_get_err(ret), pkt->size);
        return;
    }

    /* read all the output frames (infile general there may be any number of them */
    while (ret >= 0) {
        // 對于frame, avcodec_receive_frame內(nèi)部每次都先調(diào)用
        ret = avcodec_receive_frame(dec_ctx, frame);
        if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
            return;
        else if (ret < 0) {
            fprintf(stderr, "Error during decoding\n");
            exit(1);
        }
        static int s_print_format = 0;
        if (s_print_format == 0) {
            s_print_format = 1;
            print_video_format(frame);
        }

        // 一般H264默認(rèn)為 AV_PIX_FMT_YUV420P, 具體怎么強(qiáng)制轉(zhuǎn)為 AV_PIX_FMT_YUV420P 在音視頻合成輸出的時候講解
        // frame->linesize[1]  對齊的問題
        // 正確寫法  linesize[]代表每行的字節(jié)數(shù)量,所以每行的偏移是linesize[]
        for (int j = 0; j < frame->height; j++)
            fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
        for (int j = 0; j < frame->height / 2; j++)
            fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width / 2, outfile);
        for (int j = 0; j < frame->height / 2; j++)
            fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width / 2, outfile);

        // 錯誤寫法 用source.200kbps.766x322_10s.h264測試時可以看出該種方法是錯誤的
        //  寫入y分量
//        fwrite(frame->data[0], 1, frame->width * frame->height,  outfile);//Y
//        // 寫入u分量
//        fwrite(frame->data[1], 1, (frame->width) *(frame->height)/4,outfile);//U:寬高均是Y的一半
//        //  寫入v分量
//        fwrite(frame->data[2], 1, (frame->width) *(frame->height)/4,outfile);//V:寬高均是Y的一半
    }
}

// 注冊測試的時候不同分辨率的問題
// 提取H264: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264
// 提取MPEG2: ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2
// 播放:ffplay -pixel_format yuv420p -video_size 768x320 -framerate 25  source.200kbps.768x320_10s.yuv
int main(int argc, char **argv) {
    const char *outfilename;
    const char *filename;
    const AVCodec *codec;
    AVCodecContext *codec_ctx = NULL;
    AVCodecParserContext *parser = NULL;
    int len = 0;
    int ret = 0;
    FILE *infile = NULL;
    FILE *outfile = NULL;
    // AV_INPUT_BUFFER_PADDING_SIZE 在輸入比特流結(jié)尾的要求附加分配字節(jié)的數(shù)量上進(jìn)行解碼
    uint8_t inbuf[VIDEO_INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
    uint8_t *data = NULL;
    size_t data_size = 0;
    AVPacket *pkt = NULL;
    AVFrame *decoded_frame = NULL;

    if (argc <= 2) {
        fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
        exit(0);
    }
    filename = argv[1];
    outfilename = argv[2];

    pkt = av_packet_alloc();
    enum AVCodecID video_codec_id = AV_CODEC_ID_H264;
    if (strstr(filename, "264") != NULL) {
        video_codec_id = AV_CODEC_ID_H264;
    } else if (strstr(filename, "mpeg2") != NULL) {
        video_codec_id = AV_CODEC_ID_MPEG2VIDEO;
    } else {
        printf("default codec id:%d\n", video_codec_id);
    }

    // 查找解碼器
    codec = avcodec_find_decoder(video_codec_id);  // AV_CODEC_ID_H264
    if (!codec) {
        fprintf(stderr, "Codec not found\n");
        exit(1);
    }
    // 獲取裸流的解析器 AVCodecParserContext(數(shù)據(jù))  +  AVCodecParser(方法)
    parser = av_parser_init(codec->id);
    if (!parser) {
        fprintf(stderr, "Parser not found\n");
        exit(1);
    }
    // 分配codec上下文
    codec_ctx = avcodec_alloc_context3(codec);
    if (!codec_ctx) {
        fprintf(stderr, "Could not allocate audio codec context\n");
        exit(1);
    }

    // 將解碼器和解碼器上下文進(jìn)行關(guān)聯(lián)
    if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
        fprintf(stderr, "Could not open codec\n");
        exit(1);
    }

    // 打開輸入文件
    infile = fopen(filename, "rb");
    if (!infile) {
        fprintf(stderr, "Could not open %s\n", filename);
        exit(1);
    }
    // 打開輸出文件
    outfile = fopen(outfilename, "wb");
    if (!outfile) {
        av_free(codec_ctx);
        exit(1);
    }

    // 讀取文件進(jìn)行解碼
    data = inbuf;
    data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);

    while (data_size > 0) {
        if (!decoded_frame) {
            if (!(decoded_frame = av_frame_alloc())) {
                fprintf(stderr, "Could not allocate audio frame\n");
                exit(1);
            }
        }

        ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                               data, data_size,
                               AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);
        if (ret < 0) {
            fprintf(stderr, "Error while parsing\n");
            exit(1);
        }
        data += ret;   // 跳過已經(jīng)解析的數(shù)據(jù)
        data_size -= ret;   // 對應(yīng)的緩存大小也做相應(yīng)減小

        if (pkt->size)
            decode(codec_ctx, pkt, decoded_frame, outfile);

        if (data_size < VIDEO_REFILL_THRESH)    // 如果數(shù)據(jù)少了則再次讀取
        {
            memmove(inbuf, data, data_size);    // 把之前剩的數(shù)據(jù)拷貝到buffer的起始位置
            data = inbuf;
            // 讀取數(shù)據(jù) 長度: VIDEO_INBUF_SIZE - data_size
            len = fread(data + data_size, 1, VIDEO_INBUF_SIZE - data_size, infile);
            if (len > 0)
                data_size += len;
        }
    }

    /* 沖刷解碼器 */
    pkt->data = NULL;   // 讓其進(jìn)入drain mode
    pkt->size = 0;
    decode(codec_ctx, pkt, decoded_frame, outfile);

    fclose(outfile);
    fclose(infile);

    avcodec_free_context(&codec_ctx);
    av_parser_close(parser);
    av_frame_free(&decoded_frame);
    av_packet_free(&pkt);

    printf("main finish, please enter Enter and exit\n");
    return 0;
}

程序輸入?yún)?shù)判斷

if (argc <= 2) {
    fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]);
    exit(0);
}
decode_video <input file> <output file>

根據(jù)文件后綴,判斷AV_CODEC_ID_H264|AV_CODEC_ID_MPEG2VIDEO

if (strstr(filename, "264") != NULL) {
    video_codec_id = AV_CODEC_ID_H264;
} else if (strstr(filename, "mpeg2") != NULL) {
    video_codec_id = AV_CODEC_ID_MPEG2VIDEO;
} else {
    printf("default codec id:%d\n", video_codec_id);
}

查找解碼器

codec = avcodec_find_decoder(video_codec_id);  // AV_CODEC_ID_H264

根據(jù)codec->id初始化裸流解析器 AVCodecParserContext(數(shù)據(jù)) + AVCodecParser(方法)

parser = av_parser_init(codec->id);

分配解碼器codec上下文

odec_ctx = avcodec_alloc_context3(codec);

打開解碼器和解碼器上下文進(jìn)行關(guān)聯(lián)

if (avcodec_open2(codec_ctx, codec, NULL) < 0) {
    fprintf(stderr, "Could not open codec\n");
    exit(1);
}

讀取原始裸流

data = inbuf;
data_size = fread(inbuf, 1, VIDEO_INBUF_SIZE, infile);

解析出一個完整的數(shù)據(jù)包

ret = av_parser_parse2(parser, codec_ctx, &pkt->data, &pkt->size,
                       data, data_size,
                       AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

將數(shù)據(jù)包發(fā)送給解碼器解碼

ret = avcodec_send_packet(dec_ctx, pkt);
if (ret == AVERROR(EAGAIN)) {
    fprintf(stderr, "Receive_frame and send_packet both returned EAGAIN, which is an API violation.\n");
} else if (ret < 0) {
    fprintf(stderr, "Error submitting the packet to the decoder, err:%s, pkt_size:%d\n",
            av_get_err(ret), pkt->size);
    return;
}

接收解碼后的幀數(shù)據(jù)

while (ret >= 0) {
    // 對于frame, avcodec_receive_frame內(nèi)部每次都先調(diào)用
    ret = avcodec_receive_frame(dec_ctx, frame);
    if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)
        return;
    else if (ret < 0) {
        fprintf(stderr, "Error during decoding\n");
        exit(1);
    }
    static int s_print_format = 0;
    if (s_print_format == 0) {
        s_print_format = 1;
        print_video_format(frame);
    }
    ......略
 }
while 循環(huán)讀幀

while 循環(huán)讀幀 根據(jù)ret == AVERROR(EAGAIN) || ret == AVERROR_EOF 判斷

寫入解碼后的YUV數(shù)據(jù)/YUV420P格式寫入文件

// 正確寫法  linesize[]代表每行的字節(jié)數(shù)量,所以每行的偏移是linesize[]
for (int j = 0; j < frame->height; j++)
    fwrite(frame->data[0] + j * frame->linesize[0], 1, frame->width, outfile);
for (int j = 0; j < frame->height / 2; j++)
    fwrite(frame->data[1] + j * frame->linesize[1], 1, frame->width / 2, outfile);
for (int j = 0; j < frame->height / 2; j++)
    fwrite(frame->data[2] + j * frame->linesize[2], 1, frame->width / 2, outfile);

循環(huán)解碼結(jié)束沖刷解碼器

/* 沖刷解碼器 */
pkt->data = NULL;   // 讓其進(jìn)入drain mode
pkt->size = 0;
decode(codec_ctx, pkt, decoded_frame, outfile);

釋放環(huán)境

fclose(outfile);
fclose(infile);

avcodec_free_context(&codec_ctx);
av_parser_close(parser);
av_frame_free(&decoded_frame);
av_packet_free(&pkt);

程序運(yùn)行

width: 766
height: 322
format: 0
main finish, please enter Enter and exit

Process finished with exit code 0
ffplay -pixel_format yuv420p -video_size 766x322 -framerate 25 source.200kbps.766x322_10s.yuv

附錄

分離H264或mpeg2video視頻格式數(shù)據(jù)

提取H264:

ffmpeg -i source.200kbps.768x320_10s.flv -vcodec libx264 -an -f h264 source.200kbps.768x320_10s.h264

提取MPEG2:

ffmpeg -i source.200kbps.768x320_10s.flv -vcodec mpeg2video -an -f mpeg2video source.200kbps.768x320_10s.mpeg2v

播放YUV

ffplay -pixel_format yuv420p -video_size 766x322 -framerate 25 source.200kbps.766x322_10s.yuv

FFmpeg命令查找重定向

?如我們在-f fmt打算指定格式時,怎么知道什么樣的格式才是適合的format?

可以通過ffmpeg -formats | grep xx的?式去查找。

查找Audio的裸流解復(fù)?器

ffmpeg -formats | grep audio
 D  aea             MD STUDIO audio
 DE daud            D-Cinema audio
  E mp2             MP2 (MPEG audio layer 2)
 DE mp3             MP3 (MPEG audio layer 3)
 DE oma             Sony OpenMG audio
 D  wsaud           Westwood Studios audio
 D  wve             Psion 3 audio

查找Video的裸流解復(fù)?器

ffmpeg -formats | grep video

  libswresample   3.  7.100 /  3.  7.100
  libpostproc    55.  7.100 / 55.  7.100
  E a64             a64 - video for Commodore 64
 DE avs2            raw AVS2-P2/IEEE1857.4 video
 DE cavsvideo       raw Chinese AVS (Audio Video Standard) video
 D  cdxl            Commodore CDXL video
 DE h264            raw H.264 video
 DE hevc            raw HEVC video
 D  iv8             IndigoVision 8000 video
 DE m4v             raw MPEG-4 video
 DE mjpeg           raw MJPEG video
 D  mjpeg_2000      raw MJPEG 2000 video
  E mpeg1video      raw MPEG-1 video
  E mpeg2video      raw MPEG-2 video
 D  mpegvideo       raw MPEG video
  E null            raw null video
 DE rawvideo        raw video
 D  ser             SER (Simple uncompressed video format for astronomical capturing)
 DE vc1             raw VC-1 video
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容