使用ffmpeg把视频文件拆分成多张ppm格式图片

使用ffmpeg把视频文件拆分成多张ppm格式图片

  • 思路
    获得路径 - > 解封装 -> 获取视频流的信息 -> 解编码 -> YUV420转RGB -> 以ppm格式存放到文件里。

  • 伪代码
    step1. 注册
    step2. 打开音视频文件
    step3. 获取音视频流
    step4. 查找视频类型得AVMEDIA_TYPE_VIDEO
    step5. 查找解码器
    step6. 拷贝视频流得AVCodecContext
    step7. 打开解码器
    step8. 获得视频流得总共大小并且申请堆空间
    step9. 初始化YUV转RGB
    step10. 创建循环读取视频流一帧数据并且判断是否已经是视频流最后一帧
    {
    解码一帧数据
    正式处理YUV转RGB数据类型
    保存文件为PPM格式
    }
    step11. 释放空间,关闭音视频文件

  • C语言写的全部源代码如下

#include 
#include 
#include "libavcodec/avcodec.h"
#include "libavformat/avformat.h"
#include "libavutil/pixfmt.h"
#include "libswscale/swscale.h"

void save_frame(AVFrame *pframe, int width, int height, int iframe);
int main(int argc, char *argv[])
{
    char *file_path = "/root/vidoLean/xingJiDaZhan.mp4";
    const int frameNumber = 100;

    av_register_all();
    AVFormatContext *pctx = NULL;

    if (avformat_open_input(&pctx, file_path, NULL, NULL)!=0)
    {
        return -1;
    }
    assert(avformat_find_stream_info(pctx, NULL)>=0);
    av_dump_format(pctx, 0, file_path, 0);

    int video_stream = -1;
    int i= 0;
    for (i=0; i<pctx->nb_streams; i++)
    {
        // 查找第一个视频流
        if(pctx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO)
        {
            video_stream =i;
            break;
        }
    }

    if (-1==video_stream)
    {
        printf("no video stream detected\n");
        return -1;
    }
    // pcodec_ctx 指向第一个视频流
    AVCodecContext *pcodec_ctx =
            pctx->streams[video_stream]->codec;

    AVCodec *pcodec = NULL;
    // 查找视频流对应的解码器
    pcodec = avcodec_find_decoder(pcodec_ctx->codec_id);
    if (NULL == pcodec)
    {
        printf("unsupported codec.\n");
        return -1;
    }
    //因为一定不能直接使用视频流的AVCodecContext,所以要拷贝AVCodecContext上下文
    AVCodecContext *pcodec_ctx_orig =
            avcodec_alloc_context3(pcodec);
    if (avcodec_copy_context(pcodec_ctx_orig, pcodec_ctx) != 0)
    {
        printf("couldn't copy codec context\n");
        return -1;
    }
    // 打开编解码器
    if (avcodec_open2(pcodec_ctx, pcodec, NULL) < 0)
    {
        printf("couldn't open codec\n");
        return -1;
    }

    AVFrame *pframe = av_frame_alloc();
    AVFrame *pframe_rgb = av_frame_alloc();
    assert(pframe && pframe_rgb);

    //得到原始视频流数据得总共大小
    int num_bytes = avpicture_get_size(AV_PIX_FMT_RGB24,
                                       pcodec_ctx->width, pcodec_ctx->height);
	/*av_malloc() 函数是 FFmpeg 的内存分配函数,
	它其实不过是 malloc() 函数的简单封装而已,只不过确保了内存地址对齐以提升程序的效率。
	使用它和使用 malloc() 是类似的,应注意避免内存泄漏,多重释放等问题。*/
    uint8_t *buffer = av_malloc(num_bytes * sizeof(uint8_t));

//将视频帧数据填充到新分配的 buffer 里了
    avpicture_fill(
                (AVPicture *)pframe_rgb,
                buffer,
                AV_PIX_FMT_RGB24,
                pcodec_ctx->width,
                pcodec_ctx->height
                );

    int frame_finished;
    AVPacket pkt;
    //初始化 sws 上下文,用于YUV转换rgb转换。因为视频流原始得数据是yuv,ppm数据是rgb。
    struct SwsContext *sws_ctx = sws_getContext(
                pcodec_ctx->width,
                pcodec_ctx->height,
                pcodec_ctx->pix_fmt,
                pcodec_ctx->width,
                pcodec_ctx->height,
                AV_PIX_FMT_RGB24,
                SWS_BILINEAR,
                NULL,
                NULL,
                NULL
                );
    i = 0;
	//av_read_frame()读取一帧数据
    while (av_read_frame(pctx, &pkt) >= 0)
    {
        if (pkt.stream_index != video_stream)
        {
            continue;
        }
		
		//解码一帧数据,解码后得数据给pframe,压缩数据给pkt
        avcodec_decode_video2(pcodec_ctx, pframe, &frame_finished, &pkt);
        if (!frame_finished)
            continue;

    //将 frame 从其原始的格式(pctx->pix_fmt)转换到我们期望的 RGB 格式
        sws_scale(sws_ctx, pframe->data, pframe->linesize,
                  0, pcodec_ctx->height, pframe_rgb->data, pframe_rgb->linesize);

        if (++i<=frameNumber)
        {
            save_frame(pframe_rgb, pcodec_ctx->width, pcodec_ctx->height,i);  //保存ppm图片文件
        }

    }

    av_free_packet(&pkt);
    // 释放内存
    av_free(buffer);
    av_free(pframe_rgb);
    av_free(pframe);
    // 关闭 codec
    avcodec_close(pcodec_ctx);
    avcodec_close(pcodec_ctx_orig);
    // 关闭打开的文件
    avformat_close_input(&pctx);

    return 0;
}

void save_frame(AVFrame *pframe, int width, int height, int iframe)
{
    char filename[32];
    int y;

    sprintf(filename, "frame%d.ppm", iframe);
    FILE *fp = fopen(filename, "w+");
    assert(fp!=NULL);

    fprintf(fp, "P6\n%d %d\n255\n", width, height); //  PPM 文件添加了固定的头部信息。

    for (y=0; y<height; y++)
        fwrite(pframe->data[0]+y*pframe->linesize[0], 1, width*3, fp); //ppm存储格式
    fclose(fp);
}
  • 代码使用ffmeg的api解释说明

av_register_all()
实现 解码器decoder 编码器encoder 编解码器encdec 硬件加速器hwaccel 分析器parser 的注册,实现复用器muxer 和解复用器 demuxer 的注册。

avformat_alloc_context()
该函数用于分配空间创建一个AVFormatContext对象,并且强调使用avformat_free_context方法来清理并释放该对象的空间。

avformat_open_input()
输入输出结构体AVIOContext的初始化;
输入数据的协议(例如RTMP,或者file)的识别(通过一套评分机制):1判断文件名的后缀 2读取文件头的数据进行比对;
使用获得最高分的文件协议对应的URLProtocol,通过函数指针的方式,与FFMPEG连接(非专业用词);
剩下的就是调用该URLProtocol的函数进行open,read等操作了

avformat_find_stream_info()
该函数可以读取一部分音视频数据并且获得一些相关的信息。
该函数主要用于给每个媒体流(音频/视频)的AVStream结构体赋值。大致理解一下这个函数的代码,会发现它其实已经实现了解码器的查找,解码器的打开,视音频帧的读取,视音频帧的解码等工作。换句话说,该函数实际上已经“走通”的解码的整个流程。

avcodec_find_decoder()
函数的参数是一个解码器的ID,返回查找到的解码器(没有找到就返回NULL)。
函数参数为枚举类型。通过id来循环查找相应的解码器id类型的数字。

avcodec_open2()
打开该解码器。用于初始化一个视音频编解码器的AVCodecContext。

sws_getContext(int srcW, int srcH, enum AVPixelFormat srcFormat,
int dstW, int dstH, enum AVPixelFormat dstFormat,
int flags, SwsFilter *srcFilter,
SwsFilter *dstFilter, const double *param)
成功后返回SwsContext 类型的结构体。
参数1:被转换源的宽
参数2:被转换源的高
参数3:被转换源的格式,eg:YUV、RGB……(枚举格式,也可以直接用枚举的代号表示eg:AV_PIX_FMT_YUV420P这些枚举的格式在libavutil/pixfmt.h中列出)
参数4:转换后指定的宽
参数5:转换后指定的高
参数6:转换后指定的格式同参数3的格式
参数7:转换所使用的算法,
参数8:NULL
参数9:NULL
参数10:NULL
转换所用的算法在libswscale/swscale.h中枚举

avpicture_fill(AVPicture *picture, const uint8_t *ptr,
enum AVPixelFormat pix_fmt, int width, int heig)
avpicture_fill函数将ptr指向的数据填充到picture内,但并没有拷贝,只是将picture结构内的data指针指向了ptr的数据(详解:http://www.voidcn.com/article/p-aquvaett-zg.html)

avpicture_get_size()
这里FFmpeg会帮我们计算这个格式的图片,需要多少字节来存储。联系上下文,详细解释如下地址:http://blog.sina.com.cn/s/blog_825ad93f0102yvou.html

av_new_packet(); //分配packet的数据
AVPacket保存的是解码前的数据,也就是压缩后的数据。该结构本身不直接包含数据,其有一个指向数据域的指针,FFmpeg中很多的数据结构都使用这种方法来管理数据。

av_dump_format()
作为打印程序调试用的。

av_read_frame()
作用是读取码流中的音频若干帧或者视频一帧。例如,解码视频的时候,每解码一个视频帧,需要先调用 av_read_frame()获得一帧视频的压缩数据,然后才能对该数据进行解码(例如H.264中一帧压缩数据通常对应一个NAL)。

avcodec_decode_video2()
ffmpeg中的avcodec_decode_video2()的作用是解码一帧视频数据。输入一个压缩编码的结构体AVPacket,输出一个解码后的结构体AVFrame。

sws_scale()
函数主要是用来做视频像素格式和分辨率的转换,其优势在于:可以在同一个函数里实现:1.图像色彩空间转换, 2:分辨率缩放,3:前后图像滤波处理。不足之处在于:效率相对较低,不如libyuv或shader

sws_scale()和sws_getContext()区别是什么?
答:sws_getContext()是初始化转换。sws_scale()是处理具体得转换内容。

  • 效果图

    双击运行使用ffmpeg把视频文件拆分成多张ppm格式图片_第1张图片

运行以后图
使用ffmpeg把视频文件拆分成多张ppm格式图片_第2张图片

你可能感兴趣的:(音视频学习例子,ffmpeg)