FFmpeg 3 跨平台视音频编解码入门基础

引言

FFmpeg 学习之路,个人通过多方资料阅读而得出的,做个记录,也分享给其他人,降低不必要的社会劳动力。

简介

操作系统:window 7 x64
使用IDE :Qt Creator 4.2.1
使用语言:纯C语言
FFmpeg版本:3.3.1

请务必下载源码,进行跟读

版权所有:_OE_, 转载请注明出处:http://blog.csdn.net/csnd_ayo

  • 引言
  • 简介
  • 下载
  • 介绍
    • FFmpeg 架构图
    • 变量的声明
    • 注册初始化
    • avformat_alloc_context
    • avformat_open_input
    • avformat_find_stream_info
    • 寻找视频流
    • 查找解码器
    • 打开解码器
    • av_frame_alloc
    • 图像转换
    • 计算图像占用空间
    • avpicture_fill
    • 打印上下文信息
    • 取帧 - 音视频
    • 解码
    • sws_scale
    • 保存 PPM
  • 效果
    • 视频的信息
    • 帧图片
  • 尾注

下载

还没整理好,有需要的可以留邮箱。

介绍

对于 ffmpeg 在这里我就不做介绍了,可以自行百度。在这里主要对源码进行逐一解释。

FFmpeg 架构图

FFmpeg 3 跨平台视音频编解码入门基础_第1张图片

这是根据源码的执行流程画的执行架构图,点击可查看大图

该架构图对应的讲解视频 [点击前往]

FFmpeg 3 跨平台视音频编解码入门基础_第2张图片

我们的代码将根据这张架构图的路线进行编写。
而我们这篇文章就是对每个调用过程做一个较为详细的讲解。

变量的声明

//   typedef struct AVFormatContext {
//       struct AVInputFormat *iformat;
//       struct AVOutputFormat *oformat;
//       AVIOContext *pb;
//       unsigned int nb_streams;
//       AVStream **streams;
//       char filename[1024]; /**< input or output filename */
//       ....
//   } AVFormatContext;
// AVFormatContext在FFMpeg里是一个非常重要的的结构,是其它输入、输出相关信息的一个容器
// 以上只列出了其中的部分成员
// 作为输入容器时 struct AVInputFormat *iformat; 不能为空, 其中包含了输入文件的音视频流信息,程序从输入容器从读出音视频包进行解码处理
// 作为输出容器时 struct AVOutputFormat *oformat; 不能为空, 程序把编码好的音视频包写入到输出容器中
// AVIOContext *pb: I/O上下文,通过对该变量赋值可以改变输入源或输出目的
// unsigned int nb_streams; 音视频流数量
// AVStream **streams; 音视频流
// 视频流的格式内容
AVFormatContext *pFormatCtx;
/// 解码器上下文
AVCodecContext *pCodecCtx;
/// 解码器
AVCodec *pCodec;
/// 结构性视频数据
AVFrame *pFrame, *pFrameRGB;
/// 视频数据包 (1帧)
AVPacket *packet;
uint8_t *out_buffer;

struct SwsContext *sws_ctx;

int videoStream, numBytes;
int ret, frameFinished;

注册初始化

/// 内部调用 avcodec_register_all()
/// avcodec_register_all()注册了和编解码器有关的组件:硬件加速器,
/// 解码器,编码器,Parser,Bitstream Filter。
/// av_register_all()除了调用avcodec_register_all()之外,
/// 还注册了复用器,解复用器,协议处理器。
av_register_all();

avformat_alloc_context()

// 这里为 AVFormatContext 分配了一个内存空间
pFormatCtx = avformat_alloc_context();

avformat_open_input()

/**
 * @brief : 该函数用于打开多媒体并且获得一些相关的信息
 *          保存到我们给的AVFormatContext结构
 *
 * @param : ps 函数调用成功之后处理过的AVFormatContext结构体。
 *
 * @param : filename 打开的视音频流的文件路径。
 *
 * @param : fmt 强制指定AVFormatContext中AVInputFormat的。
 *              这个参数一般情况下可以设置为NULL,
 *              这样FFmpeg可以自动检测AVInputFormat。
 *
 * @param : options 附加的一些选项,一般情况下可以设置为NULL。
 *
 * @return: 函数执行成功的话,其返回值大于等于0
 *
 * @note  : 1、打开文件 2、初始化并推测了视频文件的类型
 *          3、读取文件的头部信息,填充了文件上下文数据.
 *
 * @blog  : http://blog.csdn.net/leixiaohua1020/article/details/44064715
 */
if (avformat_open_input(&pFormatCtx, file_path, NULL, NULL) != 0) {
    printf("can't open the file. \n");
    return -1;
}

avformat_find_stream_info()

/**
 * @brief : 该函数可以检查在文件中的流的信息,读取一部分视音频数据并且获得一些相关的信息。
 * @param : ic 视频文件的句柄,在这里指的就是 AVFormatContext 视频上下文
 * @param : options 其他附加的参数属性
 * @return: 函数正常执行后返回值大于等于0。
 * @note  : 1.查找解码器
 *          2.打开解码器
 *          3.读取完整的一帧压缩编码的数据
 *          4.解码一些压缩编码数据
 * @blog  : http://blog.csdn.net/leixiaohua1020/article/details/44084321
 */
if (avformat_find_stream_info(pFormatCtx, NULL) < 0) {
    printf("Could't find stream infomation.\n");
    return -1;
}

寻找视频流

/// 找到第一个视频流
/// 循环查找视频中包含的流信息,直到找到视频类型的流
/// 便将其记录下来 保存到videoStream变量中
for (unsigned int i = 0; i < pFormatCtx->nb_streams; i++) {
    if (pFormatCtx->streams[i]->codec->codec_type
            == AVMEDIA_TYPE_VIDEO) {
        videoStream = i;
    }
}

查找解码器

/// 查找解码器
/// @blog : http://blog.csdn.net/leixiaohua1020/article/details/44084557
// 通过code ID查找一个已经注册的音视频解码器
// 查找解码器之前,必须先调用av_register_all注册所有支持的解码器
// 查找成功返回解码器指针,否则返回NULL
// 音视频解码器保存在一个链表中,查找过程中,函数从头到尾遍历链表,通过比较解码器的ID来查找
pCodecCtx = pFormatCtx->streams[videoStream]->codec;
pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

打开解码器

/**
 * @brief : 该函数用于打开并初始化一个视音频编解码器的AVCodecContext。
 *
 * @param : avctx 需要初始化的AVCodecContext。
 * @param : codec 输入的AVCodec
 * @param : options 一些选项。例如使用libx264编码的时候,
 *          “preset”,“tune”等都可以通过该参数设置。
 *
 * @return: 返回0则正确
 *
 * @blog  : http://blog.csdn.net/leixiaohua1020/article/details/44117891
 */
if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) {
    printf("Could not open codec.\n");
    return -1;
}

av_frame_alloc()

// 因为我们准备输出保存24位RGB色的PPM文件,我们必需把帧的格式从原来的转换为RGB。
// FFMPEG将为我们做这些转换。
// 在大多数项目中(包括我们的这个)我们都想把原始的帧转换成一个特定的格式。
// 让我们先为转换来申请一帧的内存
pFrame = av_frame_alloc();
pFrameRGB = av_frame_alloc();

图像转换

/**
 * @brief : 图像转换,格式转换 像素转换 图像大小转换
 *
 * @param : srcW 源图像的宽
 * @param : srcH 源图像的高
 * @param : srcFormat 源图像的像素格式
 * @param : dstW 目标图像的宽
 * @param : dstH 目标图像的高
 * @param : dstFormat 目标图像的像素格式
 * @param : flags 设定图像拉伸使用的算法
 * @return: 成功执行的话返回生成的SwsContext,否则返回NULL。
 *
 * @blog  : http://blog.csdn.net/leixiaohua1020/article/details/44305697
 */
sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height,pCodecCtx->pix_fmt,
                         pCodecCtx->width, pCodecCtx->height,AV_PIX_FMT_RGB24,
                         SWS_FAST_BILINEAR, NULL, NULL, NULL);

计算图像占用空间

/**
 * @brief : 获得这个图像占用空间的大小
 *
 * @param : pix_fmt    像素格式
 * @param : width      图片宽度
 * @param : height     图片高度
 *
 * @return: 返回图像的大小,在错误的情况下返回负值
 *
 * @note  : 即使我们申请了一帧的内存,当转换的时候,我们仍然需要一个地方来放置原始的数据。
 *          我们使用avpicture_get_size来获得我们需要的大小,然后手工申请内存空间:
 */
numBytes = avpicture_get_size(AV_PIX_FMT_RGB24,
                     pCodecCtx->width, pCodecCtx->height);

avpicture_fill()

/**
 * @brief : 填充图像到指定存储区
 *
 * @param : picture       要填写的图像数据
 * @param : ptr           存储图像数据的缓冲区
 * @param : pix_fmt       图像的像素格式
 * @param : width         图像的宽度
 * @param : height        图像的高度
 *
 * @return: 源图像所需要的字节大小,失败则返回负数
 *
 * @note  : 现在我们使用avpicture_fill来把帧和我们新申请的内存来结合。
 *          关于AVPicture的结成:AVPicture结构体是AVFrame结构体的子集
 *          AVFrame结构体的开始部分与AVPicture结构体是一样的。
 *
 */
avpicture_fill((AVPicture *) pFrameRGB, out_buffer, AV_PIX_FMT_RGB24,
                pCodecCtx->width, pCodecCtx->height);

打印上下文信息

/**
 * @brief : 打印有关输入或输出格式的详细信息,如持续时间、
 *          比特率、流、容器、程序、元数据、侧数据、编解码器和时基。
 *
 * @param ic        需要分析的上下文指针
 * @param index     流索引
 * @param url       要打印的文件路径(这个参数只是为了格式化输出,没有实质意义)
 * @param is_output 选择指定的上下文是否为输入(0)或输出(1)
 */
av_dump_format(pFormatCtx, 0, file_path, 0); //输出视频信息

取帧 - 音视频

/**
* @brief : 读取码流中的音频若干帧或者视频一帧
*
* @param : s   输入的 AVFormatContext 视频上下文
* @param : pkt 输出 AVPacket
*
* @note  : 解码视频的时候,每解码一个视频帧,
*          需要先调用 av_read_frame()获得一帧视频的
*          压缩数据,然后才能对该数据进行解码
*
* @blog  : http://blog.csdn.net/leixiaohua1020/article/details/12678577
*/
if (av_read_frame(pFormatCtx, packet) < 0) {
   break; // 这里认为视频读取完了
}

解码

/**
* @brief : 解码一帧视频数据
*
* @param : avctx 解码器上下文
* @param : picture 输出一个解码后的结构体AVFrame
* @param : got_picture_ptr 没得到数据他是0 得到数据他是非0。
* @param : avpkt 解析的包
*
* @return: 错误的情况下返回负值,否则返回0或字节数
*
* @blog  : http://blog.csdn.net/leixiaohua1020/article/details/12679719
*/
ret = avcodec_decode_video2(pCodecCtx, pFrame,
                           &frameFinished, packet);

sws_scale()

/**
 * @brief : 处理图像数据,用于转换像素的函数.根据窗口有无拉伸对图像进行转换
 *
 * @param : c           经过 sws_getContext() 创建的上下文
 * @param : srcSlice    解码后的数据源
 * @param : srcStride   源图片的数据步长
 * @param : srcSliceY
 * @param : srcSliceH   源切片的高度,即切片中的行数
 * @param : dst         包含目标图像平面指针的数组
 * @param : dstStride   目标图片的数据步长
 *
 * @return: 输出片的高度
 *
 * @blog  : http://blog.csdn.net/leixiaohua1020/article/details/44346687
 */
sws_scale(sws_ctx,pFrame->data, pFrame->linesize,
          0, pCodecCtx->height,
          pFrameRGB->data, pFrameRGB->linesize);

保存 PPM

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

    // Open file
    sprintf(szFilename, "frame%d.ppm", index);
    pFile=fopen(szFilename, "wb");

    if(pFile==NULL)
        return;

    // Write header
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    // Write pixel data
    for(y=0; ydata[0]+y*pFrame->linesize[0], 1, width*3, pFile);

    // Close file
    fclose(pFile);

}

效果

视频的信息

FFmpeg 3 跨平台视音频编解码入门基础_第3张图片

帧图片

FFmpeg 3 跨平台视音频编解码入门基础_第4张图片

这里的图片因为格式特殊,而且保存的比较简练,常规的图片查看器是无法查看图片的。

尾注

在此,文章的总结大多源自 雷霄骅 的博客,感谢博士的分析于分享。博士,一路走好。

你可能感兴趣的:(ffmpeg,音频,视频,流媒体,跨平台)