基于 FFmpeg 与SDL 的视频播放器 (2)—FFmpeg视频解码

基于 FFmpeg 与SDL 的视频播放器 (2)—FFmpeg视频解码

视频解码知识

  • 解码h.264格式类型的纯净码流

    ​ 具体就是讲压缩的视屏编码数据转化为视频像素数据,例如解码H.264,就是H.264码流解码为YUV格式的数据。

  • 解码封装后的视频文件

    ​ 我们所观看的视频,码流大部分都存储在一定的封装格式(例如MP4、AVI等)中。封装格式中通常还包含音频码流等内容。对于封装格式中的视频,需要先从封装格式中提取视频码流,然后再进行解码。例如解码avi格式的视频文件,就是“avi->H.264码流->YUV”。

基于QT搭建FFmpeg开发环境

  1. 获取FFmpeg

    FFmpeg获取地址

在这里插入图片描述
根据自己的需要选择相应版本,其中其中 Static 顾名思义就是静态库版本了,Shared则是动态库版本,Dev则是提供给开发者用的Lib文件。这里我们下载的是Shared和Dev。

  1. 在项目中引用FFmpeg

项目的创建过程这里不再描述,只讲解FFmpeg引入部分,首先解压刚才下载的Shared和Dev压缩包,其中Shared压缩包中只有一个项目运行时所需要的.dll文件,Dev压缩包解压后我们只需要其中的include和lib文件夹,直接复制到我们的工程目录下面
在这里插入图片描述
然后就是在.pro文件中引入

INCLUDEPATH += $$PWD/lib/ffmpeg/include

LIBS += $$PWD/lib/ffmpeg/lib/avcodec.lib\
        $$PWD/lib/ffmpeg/lib/avdevice.lib\
        $$PWD/lib/ffmpeg/lib/avfilter.lib\
        $$PWD/lib/ffmpeg/lib/avformat.lib\
        $$PWD/lib/ffmpeg/lib/avutil.lib\
        $$PWD/lib/ffmpeg/lib/postproc.lib\
        $$PWD/lib/ffmpeg/lib/swresample.lib\
        $$PWD/lib/ffmpeg/lib/swscale.lib

由于我们建立的是C++的工程,编译的时候使用的C++的编译器编译,而FFMPEG是C的库,因此这里需要加上extern “C”。

extern "C" {
#include <libavcodec\avcodec.h>
#include <libavformat\avformat.h>
#include <libswscale\swscale.h>
#include <libswresample\swresample.h>
}

解码相关函数和数据结构介绍

函数
  1. **av_register_all():**注册所有文件格式和编解码库

  2. **avformat_open_input():**打开文件或URL,并使基于字节流的底层输入模块得到初始化;解析多媒体文件或多媒体流的头信息,创建AVFormatContext结构并填充其中的关键字段,依次为各个原始流建立AVStream结构。

  3. **avformat_find_stream_info():**获取视频文件信息。

  4. **avcodec_find_decoder():**查找解码器。

  5. *int av_seek_frame(AVFormatContext s, int stream_index, int64_t timestamp, int flags):
    作用:通过改变媒体文件的读写指针来实现对媒体文件的随机访问,大多源于媒体播放器的快进、快退等功能。
    参数:

    s:AVFormatContext指针;
    avformat_open_input返回得到。
    stream_index:指定媒体流。
    timestamp:时间标签。
    flags:定位方式

  6. **avcodec_open2():**打开解码器。

  7. **av_read_frame():**从输入文件读取一帧压缩数据。

  8. **avcodec_decode_video2():**解码一帧压缩数据。

  9. **avcodec_close():**关闭解码器。

  10. **avformat_close_input():**关闭输入视频文件。

数据结构

在这里插入图片描述

  1. AVFormatContext : 封装格式上下文结构体,也是统领全局的结构体,保存了视频文件封装 格式相关信息
  • iformat:输入视频的AVInputFormat
  • nb_streams :输入视频的AVStream 个数
  • streams :输入视频的AVStream []数组
  • duration :输入视频的时长(以微秒为单位)
  • bit_rate :输入视频的码率
  1. **AVInputFormat :**每种封装格式(例如FLV, MKV, MP4, AVI)对应一个该结构体
  • name:封装格式名称
  • long_name:封装格式的长名称
  • extensions:封装格式的扩展名
  • id:封装格式ID
  • 一些封装格式处理的接口函数
  1. **AVStream :**视频文件中每个视频(音频)流对应一个该结构体。
  • id:序号
  • codec:该流对应的AVCodecContext
  • time_base:该流的时基
  • r_frame_rate:该流的帧率
  1. **AVCodecContext :**编码器上下文结构体,保存了视频(音频)编解码相关信息。
  • codec:编解码器的AVCodec
  • width, height:图像的宽高(只针对视频)
  • pix_fmt:像素格式(只针对视频)
  • sample_rate:采样率(只针对音频)
  • channels:声道数(只针对音频)
  • sample_fmt:采样格式(只针对音频)
  1. **AVCodec :**每种视频(音频)编解码器(例如H.264解码器)对应一个该结构体。
  • name:编解码器名称
  • long_name:编解码器长名称
  • type:编解码器类型
  • id:编解码器ID
  • 一些编解码的接口函数
  1. **AVPacket :**存储一帧压缩编码数据。
  • pts:显示时间戳
  • dts :解码时间戳
  • data :压缩编码数据
  • size :压缩编码数据大小
  • stream_index :所属的AVStream
  1. **AVFrame :**存储一帧解码后像素(采样)数据。
  • data:解码后的图像像素数据(音频采样数据)。
  • linesize:对视频来说是图像中一行像素的大小;对音频来说是整个音 频帧的大小。
  • width, height:图像的宽高(只针对视频)。
  • key_frame:是否为关键帧(只针对视频) 。
  • pict_type:帧类型(只针对视频) 。例如I,P,B。
FFmpeg解码的流程

在这里插入图片描述

解码视频并保存50张图片的完整代码

#include <iostream>
using namespace std;

#define __STDC_CONSTANT_MACROS

extern "C"{
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
    #include "libavutil/pixfmt.h"
    #include "libswscale/swscale.h"
}
///由于我们建立的是C++的工程
///编译的时候使用的C++的编译器编译
///而FFMPEG是C的库
///因此这里需要加上extern "C"
///否则会提示各种未定义
#include <stdio.h>

///现在我们需要做的是让SaveFrame函数能把RGB信息定稿到一个PPM格式的文件中。
///我们将生成一个简单的PPM格式文件,请相信,它是可以工作的。
void SaveFrame(AVFrame *pFrame, int width, int height,int index)
{

FILE *pFile;
  char szFilename[32];
  int  y;

  // Open file
  sprintf(szFilename, "d:/frame%d.ppm", index);
  pFile=fopen(szFilename, "wb");
  cout<<szFilename;
  if(pFile==NULL)
    return;

  // Write header
  fprintf(pFile, "P6 %d %d 255", width, height);

  // Write pixel data
  for(y=0; y<height; y++)
  {
    fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile);
  }
  // Close file
  fclose(pFile);

}


int main(int argc, char *argv[])
{
    char *file_path = "E:1.avi";

    AVFormatContext *pFormatCtx;
    AVCodecContext *pCodecCtx;
    AVCodec *pCodec;
    AVFrame *pFrame, *pFrameRGB;
    AVPacket *packet;
    uint8_t *out_buffer;

    static struct SwsContext *img_convert_ctx;

    int videoStream, i, numBytes;
    int ret, got_picture;

    av_register_all(); //初始化FFMPEG  调用了这个才能正常适用编码器和解码器

    //Allocate an AVFormatContext.
    pFormatCtx = avformat_alloc_context();

    if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {
        printf("can't open the file.");
        return -1;
    }

    if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
        printf("Could't find stream infomation.");
        return -1;
    }

    videoStream = -1;

    ///循环查找视频中包含的流信息,直到找到视频类型的流
    ///便将其记录下来 保存到videoStream变量中
    ///这里我们现在只处理视频流  音频流先不管他
    for (i = 0; i < pFormatCtx->nb_streams; i++) {
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {
            videoStream = i;
        }
    }

    ///如果videoStream为-1 说明没有找到视频流
    if (videoStream == -1) {
        printf("Didn't find a video stream.");
        return -1;
    }

    ///查找解码器
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

    if (pCodec == NULL) {
        printf("Codec not found.");
        return -1;
    }

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

    pFrame = av_frame_alloc();
    pFrameRGB = av_frame_alloc();

    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,
            pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height,
            AV_PIX_FMT_RGB32, SWS_BICUBIC, NULL, NULL, NULL);

    numBytes = avpicture_get_size(AV_PIX_FMT_RGB32, pCodecCtx->width,pCodecCtx->height);

    out_buffer = (uint8_t *) av_malloc(numBytes * sizeof(uint8_t));
    avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB32,
            pCodecCtx->width, pCodecCtx->height);

    int y_size = pCodecCtx->width * pCodecCtx->height;

    packet = (AVPacket *) malloc(sizeof(AVPacket)); //分配一个packet
    av_new_packet(packet, y_size); //分配packet的数据

    av_dump_format(pFormatCtx, 0, file_path, 0); //输出视频信息

    int index = 0;

    
    //循环
    while (1)
    {
        if (av_read_frame(pFormatCtx, packet) < 0)
        {
            break; //这里认为视频读取完了
        }

        if (packet->stream_index == videoStream) {
            //这里就是将读取的每帧数据进行解码
            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture,packet);

            if (ret < 0) {
                printf("decode error.");
                return -1;
            }

            if (got_picture) {
                //通过该函数将解码的YUV格式文件转化为RGB格式文件
                sws_scale(img_convert_ctx,
                        (uint8_t const * const *) pFrame->data,
                        pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                        pFrameRGB->linesize);

                SaveFrame(pFrameRGB, pCodecCtx->width,pCodecCtx->height,index++); //保存图片
                if (index > 50) return 0; //这里我们就保存50张图片
            }
        }
        av_free_packet(packet);
    }
    av_free(out_buffer);
    av_free(pFrameRGB);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);

    return 0;
}
发布了24 篇原创文章 · 获赞 5 · 访问量 3934

猜你喜欢

转载自blog.csdn.net/qq_41345281/article/details/104232915