[音视频] ffmpeg开发环境搭建&简介

一、创建工程

在VS中新建一个Win32控制台程序,然后做一下几个操作:

1.拷贝FFmpeg的几种开发文件到项目目录下

其中包含include文件夹、lib文件夹和动态库文件(.dll)。

2.在VS中进行以下配置:

1) 配置属性-->C/C++-->常规-->附加包含目录,输入"include"(项目目录下的include)。

2)配置属性-->连接器-->常规-->附加库目录,输入"lib"(项目目录下的lib)

3)配置属性-->连接器-->输入-->附加依赖项,输入lib目录下所有"*.lib"文件,用分号隔开

4)动态库不用配置。

3.测试是否成功

编写代码:

// testffmpeg.cpp : 定义控制台应用程序的入口点。
//

#define __STDC_CONSTANT_MACROS

#include "stdafx.h"

extern "C"
{
    #include "libavcodec\avcodec.h"
}


int main()
{
    printf("%s", avcodec_configuration());
    getchar();
    return 0;
}

可以看到运行结果:

 

说明开发环境配置成功。

二、库和流程介绍

FFmpeg一共包含8个库:

  • avcodec:编解码(最重要)。
  • avformat:封装格式处理(重要)。
  • avfilter:滤镜特效处理。
  • avdevice:各种设备的输入输出。
  • avutil:工具库(大部分库需要这个库的支持)。
  • postproc:后加工。
  • swresample:音频采样数据格式转换。
  • swscale:视频像素数据格式转换。

1.视频读取和解码流程

流程描述:

1.ac_register_all():注册所有组件。一般在使用ffmpeg的开始都会使用。

2.avformat_open_input():打开输入。例如文件、网络流等。

3.avformat_find_stream_info():查找流信息,例如文件中包含音频、视频等多条码流。

4.avcodec_find_devoder():查找流对应的解码器。

5.avcodec_open2():打开解码器。

6.av_read_frame():读取一帧压缩后的数据。

7.AVPacket:一个结构体,用于存放一帧压缩后的数据。这里用于存放av_read_frame读取到的数据。

8.avcodec_devode_video2():使用解码器解码AVPacket中的数据。

9.AVFrame:一个结构体,用于存放解码后的YUV数据(原始图像)。

10.显示在输出设备。

11.avcodec_close():关闭解码器。

12.avformat_close_input():关闭打开的码流。

其中6、7、8、9、10为循环过程,即每次读取一帧,解码,显示。如果没读到帧数据,说明整个码流结束。

三.数据结构介绍

FFmpeg中包含以下一些基本的数据结构:

 

1.AVFormatConext:视频格式的上下文,其中包含封装信息,多个码流的结构体。

2.AVInputFormat:保存封装信息。

3.AVStream:这是一个数组,一般0表示视频码流,1表示音频码流。

4.AVCodecContext:解码器上下文,其中包含解码器和相关信息。

5.AVCodec:解码器

6.AVPacket:用于存放压缩后的视频数据。

7.AVFrame:用于存放解码后的数据。

 

1.AVFormatContext

该结构体包含以下几个变量:

  • iformat:输入视频的AVInputFormat结构体指针。
  • nb_streams:输入视频的AVStream个数。
  • streams:输入视频的AVStream[]数组,其中包含视频和音频码流等。
  • duration:输入视频的时长(以毫秒为单位)。
  • bit_rate:输入视频的码率。
  • 等等。

2.AVInputFormat

该结构体包含以下几个变量:

  • name:封装格式名称,例如FLV等。
  • long_name:封装格式长名称,例如FLV<FLASH VIDEO>。
  • extensions:封装格式的扩展名。
  • id:封装格式ID。
  • 一些封装格式处理的接口函数指针。

3.AVStream

  • id:序号
  • codec:该流对应的AVCodecContext结构体指针。
  • time_base:该流的时基。用来计算每一帧播放时间的时基(乘数)。
  • r_frame_rate:该流的帧率。一秒有多少帧画面。
  • 等等。

4.AVCodecContext

  • codec:编解码器的AVCodec结构体指针。
  • width,height:图像的宽高(只针对视频)。
  • pix_fmt:像素格式(只针对视频)。
  • sample_rate:采样率(只针对音频)。
  • channels:声道数(只针对音频)。
  • sample_fmt:采样格式(只针对音频)。
  • 等等。

5.AVCodec

  • name:编解码器名称。
  • long_name:编解码器长名称。
  • type:编解码器类型,音频&视频。
  • id:编解码器ID。
  • 一些编解码的接口函数指针。

6.AVPacket

  • pts:显示时间戳,通过与时基一起计算出具体时间。(由于解码顺序和显示顺序是不同的,所以有pts和dts两个时间戳,后面详细解释)
  • dts:解码时间戳。
  • data:压缩编码数据。
  • size:压缩编码数据大小。
  • stream_index:所属的AVStream。

7.AVFrame

  • data:解码后的图像像素数据(音频采样数据)。
  • linesize:对视频来说就是图像中一行像素的大小;对音频来说就是整个音频帧的大小。
  • width,height:图像的高宽(只针对视频)。
  • key_frame:是否为关键帧(只针对视频)。
  • pict_type:帧类型(只针对视频)。例如I,P,B。

四、实例

打开一个视频文件,将其内容读取出来,写入h264文件,yuv文件:

#include <stdio.h>

#define __STDC_CONSTANT_MACROS

#include "stdafx.h"

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

#define SWS_BICUBIC 4

int main(int argc, char* argv[])
{
    AVFormatContext    *pFormatCtx;
    int                i, videoindex;
    AVCodecContext    *pCodecCtx;
    AVCodec            *pCodec;
    AVFrame    *pFrame, *pFrameYUV;
    uint8_t *out_buffer;
    AVPacket *packet;
    int y_size;
    int ret, got_picture;
    struct SwsContext *img_convert_ctx;
    //输入文件路径
    char filepath[] = "output.mp4";  // 需要处理的视频文件名,在当前目录下

    int frame_cnt;

    av_register_all();  // 注册所有组件
    avformat_network_init();  // 初始化网络,例如可以读取rtsp的视频流
    pFormatCtx = avformat_alloc_context();  // 分配一个AVFormatContext空间

    if (avformat_open_input(&pFormatCtx, filepath, NULL, NULL) != 0) {  // 打开视频文件
        printf("Couldn't open input stream.\n");
        return -1;
    }
    if (avformat_find_stream_info(pFormatCtx, NULL)<0) {  // 查找流信息
        printf("Couldn't find stream information.\n");
        return -1;
    }
    videoindex = -1;
    for (i = 0; i<pFormatCtx->nb_streams; i++)   // 找到视频码流,并记录其编号,默认一般为0(在不确定的情况下,要进行判断)
        if (pFormatCtx->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) {  // AVMEDIA_TYPE_VIDEO 是枚举值,代表视频码流
            videoindex = i;  // 获取到视频码流的编号
            break;
        }
    
    if (videoindex == -1) {
        printf("Didn't find a video stream.\n");
        return -1;
    }

    pCodecCtx = pFormatCtx->streams[videoindex]->codec;  // 获取视频码流的编解码器上下文结构体
    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.\n");
        return -1;
    }
    /*
    * 在此处添加输出视频信息的代码
    * 取自于pFormatCtx,使用fprintf()
    */
    pFrame = av_frame_alloc();  // 分配一个AVFrame内存空间,用于存放解码后的YUV数据
    pFrameYUV = av_frame_alloc();  // 另外再分配一个AVFrame空间,用于存放sws_scale处理后的数据(剪裁掉无数据部分)
    out_buffer = (uint8_t *)av_malloc(avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height));
    avpicture_fill((AVPicture *)pFrameYUV, out_buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    packet = (AVPacket *)av_malloc(sizeof(AVPacket));  // 分配AVPacket内存空间,用于存放解码前的帧数据
                                                       //Output Info-----------------------------
    printf("--------------- File Information ----------------\n");
    av_dump_format(pFormatCtx, 0, filepath, 0);
    printf("-------------------------------------------------\n");
    img_convert_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt,
        pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_YUV420P, SWS_BICUBIC, NULL, NULL, NULL);

    // 打开一个用于存放h264数据的文件
    FILE * fp_264 = fopen("test264.h264", "wb+");

    // 打开一个用于存放YUV数据的文件
    FILE * fp_yuv = fopen("testyuv.yuv", "wb+");

    frame_cnt = 0;  // 帧计数
    while (av_read_frame(pFormatCtx, packet) >= 0) {  // 循环读取帧数据
        if (packet->stream_index == videoindex) {  // 判断读取到的帧是否为视频帧
                                                   /*
                                                   * 在此处添加输出H264码流的代码
                                                   * 取自于packet,使用fwrite()
                                                   */
                                                   // 将每一帧的未解码数据(h264数据)写入文件中,第一个参数是需要写入文件的数据,第二个参数是每次写入的字节,第三个参数是写入次数,第四个参数是文件句柄
            fwrite(packet->data, 1, packet->size, fp_264);

            ret = avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, packet);  // 使用解码器进行解码
            if (ret < 0) {
                printf("Decode Error.\n");
                return -1;
            }
            if (got_picture) {
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data, pFrame->linesize, 0, pCodecCtx->height,
                    pFrameYUV->data, pFrameYUV->linesize);   // 对解码后的YUV数据进行剪裁(直接解码得到的YUV,可能存在空白部分)
                //printf("Decoded frame index: %d\n", frame_cnt);  // 打印当前是第多少帧

                                                                 /*
                                                                 * 在此处添加输出YUV的代码
                                                                 * 取自于pFrameYUV,使用fwrite()
                                                                 */
                fwrite(pFrameYUV->data[0], 1, pCodecCtx->width*pCodecCtx->height, fp_yuv);   // 写入Y数据,大小是宽*高
                fwrite(pFrameYUV->data[1], 1, pCodecCtx->width*pCodecCtx->height/4, fp_yuv);   // 写入U数据,注意U数据只有1/4大小
                fwrite(pFrameYUV->data[2], 1, pCodecCtx->width*pCodecCtx->height/4, fp_yuv);   // 写入V数据,注意V数据只有1/4大小

                frame_cnt++;

            }
        }
        av_free_packet(packet);  // 释放AVPacket空间?还是清除其中的数据?调用位置可能有问题
    }

    fclose(fp_264);
    fclose(fp_yuv);

    sws_freeContext(img_convert_ctx);  // 释放sws context对象

    av_frame_free(&pFrameYUV);  // 释放AVFrame空间
    av_frame_free(&pFrame);  // 释放AVFrame空间
    avcodec_close(pCodecCtx);  // 关闭解编码器
    avformat_close_input(&pFormatCtx);  // 关闭AVFormatContext

    return 0;
}

 

 

= =!

 

posted @ 2021-01-26 14:38  风间悠香  阅读(339)  评论(0编辑  收藏  举报