1. 程式人生 > 其它 >FFmpeg菜雞互啄#第3篇#視訊解碼

FFmpeg菜雞互啄#第3篇#視訊解碼

解碼過程

基本過程:開啟輸入檔案,查詢視訊流,開啟解碼器,迴圈讀幀解碼幀,關閉解碼器,關閉輸入檔案。

解碼資料結構

Code

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

/*
#define __STDC_CONSTANT_MACROS
#ifndef INT64_C
#define INT64_C(c) (c ## LL)
#define UINT64_C(c) (c ## ULL)
#endif
*/

extern "C"
{
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libswscale/swscale.h"
#include "libavdevice/avdevice.h"
}

#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "avdevice.lib")
#pragma comment(lib, "avfilter.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "postproc.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "swscale.lib")

#define INPUT "in.flv"
#define OUTPUT "out.yuv"

int main()
{
    int res = 0;
    int videoStream = -1;//標記視訊流的編號
    char errBuf[BUFSIZ] = { 0 };
    FILE* fp_out = fopen(OUTPUT, "wb+");

    //初始化FFMPEG  呼叫了這個才能正常適用編碼器和解碼器
    av_register_all();
    printf("FFmpeg's version is: %dn", avcodec_version());

    //FFMPEG所有的操作都要通過這個AVFormatContext來進行
    AVFormatContext* pFormatCtx = NULL;

    //開啟輸入視訊檔案
    //Open an input stream and read the header. The codecs are not opened.
    if ((res = avformat_open_input(&pFormatCtx, INPUT, NULL, NULL)) < 0)
    {
        av_strerror(res, errBuf, sizeof(errBuf));
        printf("%sn", errBuf);
        return -1;
    }
    //Read packets of a media file to get stream information. This is useful for file formats with no headers such as MPEG.
    //相當於對輸入進行 “預處理”
    avformat_find_stream_info(pFormatCtx, NULL);
    av_dump_format(pFormatCtx, 0, NULL, 0); //輸出視訊流的資訊

    //查詢流
    for (int i = 0; i < pFormatCtx->nb_streams; ++i)
    {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO)
            videoStream = i;
    }
    if (videoStream == -1)
    {
        printf("Didn't find a video stream.n");
        return -1;
    }

    ///查詢解碼器    
    AVCodecContext* pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    AVCodec* pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    if (pCodec == NULL)
    {
        printf("Codec not found.n");
        return -1;
    }

    ///開啟解碼器
    if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)
    {
        printf("Could not open codec.");
        return -1;
    }

    AVFrame Frame = { 0 };//不初始化,avcodec_decode_video2會報錯
    AVPacket packet;
    int got_picture;
    while (1)
    {
        //讀取視訊幀
        //return 0 if OK, < 0 on error or end of file
        if (av_read_frame(pFormatCtx, &packet) < 0)
        {
            break; //這裡認為視訊讀取完了
        }
        if (packet.stream_index == videoStream)
        {
            //解碼視訊幀
            if (avcodec_decode_video2(pCodecCtx, &Frame, &got_picture, &packet) < 0)
            {
                printf("decode error.n");
                return -1;
            }
            if (got_picture)
            {
                if (Frame.format == PIX_FMT_YUV420P)
                {
                    //解碼後YUV格式的視訊畫素資料儲存在AVFrame的data[0]、data[1]、data[2]中。
                    //但是這些畫素值並不是連續儲存的,每行有效畫素之後儲存了一些無效畫素。
                    //以亮度Y資料為例,data[0]中一共包含了linesize[0] * height個數據。
                    //但是出於優化等方面的考慮,linesize[0]實際上並不等於寬度width,而是一個比寬度大一些的值。
                    fwrite(Frame.data[0], Frame.linesize[0] * Frame.height, 1, fp_out);
                    fwrite(Frame.data[1], Frame.linesize[1] * Frame.height / 2, 1, fp_out);
                    fwrite(Frame.data[2], Frame.linesize[2] * Frame.height / 2, 1, fp_out);
                }
            }
        }
        av_free_packet(&packet);//清除packet裡面指向的緩衝區
    }

    fclose(fp_out);
    avcodec_close(pCodecCtx);//關閉解碼器
    avformat_close_input(&pFormatCtx);//關閉輸入視訊檔案。avformat_free_context(pFormatCtx);就不需要了
    return 0;
}

儲存的檔案可以用bin目錄下的YUVPlayer.exe檢視,但是要設定好解析度(Frame.linesize[0] * Frame.height)。我的30.9MB輸入解碼後得到一個3.8GB的資料!

注意最右邊有無效資料,但是可以通過以後的轉換操作去除。

Github

https://github.com/gongluck/FFmpegTest.git