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: %d\n", 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("%s\n", 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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Flutter入门

偶遇FFmpeg(番外)——FFmpeg花样编译入魔1之裁剪大小

在偶遇FFmpeg(三)——Android集成这边文章中曾经介绍过FFmpeg和Android的交叉编译。文章中也提到过如何裁剪SO文件大小的方式。 这边文章...

1383
来自专栏AI科技大本营的专栏

AI 技术讲座精选:如何利用 Python 读取数据科学中常见几种文件?

前 言 如果你是数据行业的一份子,那么你肯定会知道和不同的数据类型打交道是件多么麻烦的事。不同数据格式、不同压缩算法、不同系统下的不同解析方法——很快就会让你...

3444
来自专栏Seebug漏洞平台

CVE-2015-1641 Word 利用样本分析

00 引 子 本文我们将通过一个恶意文档的分析来理解漏洞 CVE-2015-1641(MS15-033)的具体利用过程,以此还原它在现实攻击中的应用。就目前来...

3078
来自专栏程序员互动联盟

【答疑释惑第十二讲】什么是函数库?

疑惑一 java中如何引入第三方jar包? 方法一、使用Bootstrap Classloader来加载这些类 我们可以在运行时使用如下参数: -Xbootcl...

3308
来自专栏DeveWork

初学Python 之抓取当当网图书页面目录并保存到txt文件

这学期新开了门“高大上”的课《机器学习》,也开始入门Python。然后跟我们一样初学Python 的老师布置了个“作业”——用Python 弄个抓取当当网图书页...

1845
来自专栏程序员宝库

短网址(short URL)系统的原理及其实现

作者: 小猿大圣 https://segmentfault.com/a/1190000012088345 背景 提供一个短址服务。 你有没有发现,我们的任务中出...

4024
来自专栏流媒体

Android RTMP推流之MediaCodec硬编码一(H.264进行flv封装)

在前面Android平台下使用FFmpeg进行RTMP推流(摄像头推流)的文章中,介绍了如何使用FFmpeg进行H264编码和Rtmp推流。接下来讲分几篇文章来...

1653
来自专栏极客编程

用go语言创建区块链

本文你将用Go语言创建自己的区块链、理解哈希函数是如何保持区块链的完整性、掌握如何创造并添加新的块、实现多个节点通过竞争生成块、通过浏览器来查看整个链、了解所有...

662
来自专栏生信宝典

Bookdown文档生成教程

bookdown是一款及其方便的编写技术文档或教材的工具,语法简洁,数据处理灵活。支持Rmarkdown或普通markdown通过pandoc软件转换为HTML...

2665
来自专栏生信宝典

分子对接简明教程 (二)

用PyMOL展示配体和受体相互作用的原子和氢键 为了简化展示过程,我们设计了一个pml脚本 (脚本内有很详细的解释),只需要修改脚本里面受体和配体的名字,然后在...

2545

扫码关注云+社区