音视频处理 ffmpeg中级开发 视频转图片

Posted MY CUP OF TEA

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了音视频处理 ffmpeg中级开发 视频转图片相关的知识,希望对你有一定的参考价值。

操作流程

  • 目的:使用FFmpeg将视频的每一帧数据转换为图片
  • 1,打开输入的多媒体文件,检索多媒体文件中的流信息
  • 2,查找视频流的索引号,通过索引号获取数据流;通过解析视频流中的编码参数得到解码器ID,进一步通过编码器ID获取编码器
  • 3,创建输出上下文环境,并将视频流中的编解码参数拷贝到输出上下文环境中(结构体)
  • 4,循环读取视频流中的每一帧数据进行解码
  • 5,将解码后的数据进行图像色彩的空间转换、分辨率的缩放、前后图像滤波处理等,涉及到sws_getContext()、sws_scale()、sws_freeContext()这三个函数,分别进行初始化、转换、释放。
  • 转换后 加入 图片的头部信息进行保存
  • 参考链接:
  • FFmpeg将视频转为图片 - 简书                                                                     未成功
  • FFmpeg代码实现视频转jpg图片_Geek.Fan的博客-CSDN博客_ffmpeg jpg  成功
  • 1).打开视频文件;(2)获取视频流;(3)找到对应的解码器;(4).初始化解码器上下文;(5).设置编解码器参数;(6)打开解码器;(7)读取视频帧;(8)发送等待解码帧;(9).接收解码帧数据;

补充链接

补充知识

两台Ubuntu之间通过scp进行数据的传输

  • 协议 传输的文件 接收端的账户 接收端的ip地址 文件的存储路径
  • scp test.mp4 root@192.168.253.131:/home/chy-cpabe/Videos 
  • 输入密码

avpicture_get_size

  • avpicture_get_size已经被弃用,现在改为使用av_image_get_size()
  • 具体用法如下:
    • old: avpicture_get_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    • new: //最后一个参数align这里是置1的,具体看情况是否需要置1
    • av_image_get_buffer_size(AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height, 1);

avpicture_fill

  • avpicture_fill已经被弃用,现在改为使用av_image_fill_arrays
  • 具体用法如下:
    • old: avpicture_fill((AVPicture *)pFrame, buffer, AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height);
    • new: //最后一个参数align这里是置1的,具体看情况是否需要置1
    • av_image_fill_arrays(pFrame->data, pFrame->linesize, buffer,  AV_PIX_FMT_YUV420P, pCodecCtx->width, pCodecCtx->height,1);

avcodec_decode_video2

  • 原本的解码函数被拆解为两个函数avcodec_send_packet()和avcodec_receive_frame()
  • 具体用法如下:
    • old: avcodec_decode_video2(pCodecCtx, pFrame, &got_picture, pPacket);
    • new: avcodec_send_packet(pCodecCtx, pPacket); avcodec_receive_frame(pCodecCtx, pFrame);
ret = avcodec_send_packet(pCodecCtx, packet);

got_picture = avcodec_receive_frame(pCodecCtx, pFrame); //got_picture = 0 success, a frame was returned

//注意:got_picture含义相反

或者:

int ret = avcodec_send_packet(aCodecCtx, &pkt);

if (ret != 0)



prinitf("%s/n","error");

return;




while( avcodec_receive_frame(aCodecCtx, &frame) == 0)

//读取到一帧音频或者视频

//处理解码后音视频 frame



代码

#include <cstdio>
#include <cstdlib>
#include <cstring>

extern "C" 
#include <libavformat/avformat.h>
#include<libavcodec/avcodec.h>
#include<libswscale/swscale.h>
#include <libavutil/imgutils.h>


int SavePicture(AVFrame* pFrame,char* out_name)
    int width = pFrame->width;
    int height = pFrame->height;
    AVCodecContext *pCodeCtx = nullptr;

    AVFormatContext *pFormatCtx = avformat_alloc_context();
    // 设置输出文件格式
    pFormatCtx->oformat = av_guess_format("mjpeg",NULL,NULL);
    // 创建并初始化输出AVIOContext
    if (avio_open(&pFormatCtx->pb,out_name,AVIO_FLAG_READ_WRITE) < 0)
        printf("Couldn't open output file.");
        return -1;
    
    //create stream
    AVStream *pAVStream = avformat_new_stream(pFormatCtx,0);
    if (pAVStream == NULL)
        return -1;
    
    AVCodecParameters *parameters = pAVStream->codecpar;
    parameters->codec_id = pFormatCtx->oformat->video_codec;
    parameters->codec_type = AVMEDIA_TYPE_VIDEO;
    parameters->format = AV_PIX_FMT_YUV420P;
    parameters->width = pFrame->width;
    parameters->height = pFrame->height;

    AVCodec *pCodec = const_cast<AVCodec *>(avcodec_find_encoder(pAVStream->codecpar->codec_id));
    if (!pCodec)
        printf("Could not find encoder\\n");
        return -1;
    
    pCodeCtx = avcodec_alloc_context3(pCodec);
    if (!pCodeCtx)
        fprintf(stderr,"Could not allocate video codec context\\n");
        exit(1);
    
    if ((avcodec_parameters_to_context(pCodeCtx,pAVStream->codecpar)) < 0)
        fprintf(stderr,"Failed to copy %s codec parameters to decoder context\\n",
                av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        return -1;
    
    pCodeCtx->time_base = (AVRational)1,25;
    if (avcodec_open2(pCodeCtx,pCodec,NULL) < 0)
        printf("Could not open codec.");
        return -1;
    
    int ret = avformat_write_header(pFormatCtx,NULL);
    if (ret < 0)
        printf("Write_header fail\\n");
        return -1;
    
    int y_size = width * height;
    //Encode
    //给AVPacket分配足够大的空间
    AVPacket pkt;
    av_new_packet(&pkt,y_size*3);
    //encode data
    ret = avcodec_send_frame(pCodeCtx,pFrame);
    if (ret < 0)
        printf("Could not avcodec_send_frame");
        return -1;
    
    //得到编码后数据
    ret = avcodec_receive_packet(pCodeCtx,&pkt);
    if (ret < 0)
        printf("Could not avcodec_receive_packet");
        return -1;
    
    ret = av_write_frame(pFormatCtx,&pkt);
    if (ret < 0)
        printf("Could not av_write_frame");
        return -1;
    
    av_packet_unref(&pkt);
    av_write_trailer(pFormatCtx);
    avcodec_close(pCodeCtx);
    avio_close(pFormatCtx->pb);
    avformat_free_context(pFormatCtx);
    return 0;


int main(int argc,char** argv)
    int ret = 0;
    const char* in_filename,*out_filename;
    AVFormatContext *fmt_ctx = NULL;
    const AVCodec *codec;
    AVCodecContext *codeCtx = NULL;
    AVStream *stream = NULL;
    int stream_index = 0;
    AVPacket avpkt;
    AVFrame *frame;
    int frame_count = 0;
    if (argc <= 2)
        printf("Usage:%s <input file> <output file>\\n",argv[0]);
        exit(0);
    
    in_filename = argv[1];
    out_filename = argv[2];
    if (avformat_open_input(&fmt_ctx,in_filename,NULL,NULL) < 0)
        printf("Could not open source file %s \\n",in_filename);
        exit(1);
    
    if (avformat_find_stream_info(fmt_ctx,NULL)<0)
        printf("Could not find stream information\\n");
        exit(1);
    
    av_dump_format(fmt_ctx,0,in_filename,0);
    av_init_packet(&avpkt);
    avpkt.data = NULL;
    avpkt.size = 0;
    stream_index = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_VIDEO,-1,
                                       -1,NULL,0);
    if (stream_index < 0)
        fprintf(stderr,"Could not find %s stream in input file '%s'\\n",
                av_get_media_type_string(AVMEDIA_TYPE_VIDEO),in_filename);
        return stream_index;
    
    stream = fmt_ctx->streams[stream_index];
    codec = avcodec_find_decoder(stream->codecpar->codec_id);
    if (!codec)
        return -1;
    
    codeCtx = avcodec_alloc_context3(NULL);
    if (!codeCtx)
        fprintf(stderr,"Could not allocate video codec context\\n");
        exit(1);
    
    if (avcodec_parameters_to_context(codeCtx,stream->codecpar)<0)
        fprintf(stderr,"Failed to copy %s codec parameters to decoder context\\n",
                av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        return -1;
    
    avcodec_open2(codeCtx,codec,NULL);
    frame = av_frame_alloc();
    if (!frame)
        fprintf(stderr,"Could not allocate video frame\\n");
        exit(1);
    
    char buf[1024];
    while(av_read_frame(fmt_ctx,&avpkt) >= 0)
        if (avpkt.stream_index == stream_index)
            ret = avcodec_send_packet(codeCtx,&avpkt);
            if (ret < 0)
                continue;
            
            while (avcodec_receive_frame(codeCtx,frame) == 0)
                snprintf(buf,sizeof (buf),"%s/picture-%d.jpg",out_filename,frame_count);
                SavePicture(frame,buf);
            
            frame_count++;
        
        av_packet_unref(&avpkt);
    
    return 0;

运行结果

错误代码

#include <cstdio>
#include <cstdlib>
#include <cstring>

extern "C" 
    #include <libavformat/avformat.h>
    #include<libavcodec/avcodec.h>
    #include<libswscale/swscale.h>
    #include <libavutil/imgutils.h>


#define INBUF_SIZE 4096

#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t

#pragma pack(2)
// 位图文件头(bitmap-file header)包含了图像类型、图像大小、图像数据存放地址和两个保留未使用的字段
typedef struct tagBITMAPFILEHEADER
    WORD bfType;
    DWORD bfSize;
    WORD bfReserved1;
    WORD bfReserved2;
    DWORD bfOffBits;
BITMAPFILEHEADER,*PBITMAPFILEHEADER;
// 位图信息头(bitmap-information header)包含了位图信息头的大小、图像的宽高、图像的色深、压缩说明图像数据的大小和其他一些参数。
typedef struct tagBITMAPINFOHEADER
    DWORD biSize;
    LONG  biWidth;
    LONG  biHeight;
    LONG  biPlans;
    LONG  biBitCount;
    DWORD biCompression;
    DWORD biSizeImage;
    LONG  biXPelsPerMeter;
    LONG  biYPelsPerMeter;
    DWORD biClrUsed;
    DWORD biClrImportant;
BITMAPINFOHEADER,*PBITMAPINFOHEADER;
//图片转换并保存
void saveBMP(struct SwsContext* img_convert_ctx,AVFrame* frame,char* filename)
    //1 先进行转换,  YUV420=>RGB24:
    int w = frame->width;
    int h = frame->height;
    int numBytes = av_image_get_buffer_size(AV_PIX_FMT_BGR24,w,h,1);
    uint8_t *buffer = (uint8_t *) av_malloc(numBytes * sizeof (uint8_t));
    AVFrame *pFrameRGB = av_frame_alloc();
    //buffer is going to be written to rawvideo file,no alignment
    av_image_fill_arrays(pFrameRGB->data,pFrameRGB->linesize,buffer,
                         AV_PIX_FMT_BGR24,w,h,1);
    /*
    进行转换
    frame->data和pFrameRGB->data分别指输入和输出的buf
    frame->linesize和pFrameRGB->linesize可以看成是输入和输出的每列的byte数
    第4个参数是指第一列要处理的位置
    第5个参数是source slice 的高度
    */
    sws_scale(img_convert_ctx,frame->data,frame->linesize,0,h,
              pFrameRGB->data,pFrameRGB->linesize);
    //2 create BITMAPINFOHEADER
    BITMAPINFOHEADER header;
    header.biSize = sizeof (BITMAPINFOHEADER);
    header.biWidth = w;
    header.biHeight = h*(-1);
    header.biBitCount = 24;
    header.biSizeImage = 0;
    header.biClrImportant = 0;
    header.biClrUsed = 0;
    header.biXPelsPerMeter = 0;
    header.biYPelsPerMeter = 0;
    header.biPlans = 1;
    //3 create file header
    BITMAPFILEHEADER bmpFileHeader = 0,;
    //HANDLE hFILE = NULL;
    DWORD dwTotalWriten = 0;
    DWORD dwWriten;
    bmpFileHeader.bfType = 0x4d42;//'BM'
    bmpFileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER) + numBytes;
    bmpFileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof (BITMAPINFOHEADER);

    FILE *pf = fopen(filename,"wb");
    fwrite(&bmpFileHeader,sizeof (BITMAPFILEHEADER),1,pf);
    fwrite(&header,sizeof (BITMAPINFOHEADER),1,pf);
    fwrite(pFrameRGB->data[0],1,numBytes,pf);
    fclose(pf);

    //4 释放资源
    //av_free(buffer);
    av_free(&pFrameRGB[0]);
    av_free(pFrameRGB);


static void pgm_save(unsigned char* buf,int wrap,int x_size,int y_size,char* filename)
    FILE *f;
    int i;
    f = fopen(filename,"w");
    fprintf(f,"P5\\n%d %d\\n%d\\n",x_size,y_size,255);
    for (i = 0; i < y_size; ++i) 
        fwrite(buf+i*wrap,1,x_size,f);
    
    fclose(f);


//对数据进行解码
static int decode_write_frame(const char* outfilename,AVCodecContext *avctx,struct SwsContext* img_convert_ctx,
        AVFrame *frame,int* frame_count,AVPacket *pkt,int last)
    int ret,got_frame;
    char buf[1024];
    // 对数据进行解码,avctx是编解码器上下文,解码后的帧存放在frame,通过got_frame获取解码后的帧,pkt是输入包
    ret = avcodec_send_packet(avctx,pkt);
    if (ret < 0)
        fprintf(stderr,"Error while decoding frame %d\\n",*frame_count);
        return ret;
    
    got_frame = avcodec_receive_frame(avctx,frame);
    // 解码成功
    if (got_frame)
        printf("Saving %s frame %3d\\n",last?"last":" ",*frame_count);
        fflush(stdout);
        //The picture is allocated by the decoder,no need to free it
        snprintf(buf,sizeof (buf),"%s-%d.bmp",outfilename,*frame_count);
        //解码后的数据帧保存rgb图片
        saveBMP(img_convert_ctx,frame,buf);
        (*frame_count)++;
    
    return 0;


int main(int argc,char** argv)
    int ret;
    FILE *f;
    // 输入、输出文件路径
    const char* filename,*outfilename;
    // 格式上下文环境(输入文件的上下文环境)
    AVFormatContext *fmt_ctx = nullptr;
    // 编解码器
    const AVCodec *codec;
    // 编解码器上下文环境(输出文件的上下文环境)
    AVCodecContext *codec_context = nullptr;
    // 多媒体文件中的流
    AVStream *stream = nullptr;
    // 流的index
    int stream_index= 0;
    int frame_count;
    // 原始帧
    AVFrame *frame;
    // 图片处理的上下文
    struct SwsContext* img_convert_ctx;
    //uint8_t inbuf[INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE];
   AVPacket avPacket;
    if (argc < 2)
        fprintf(stderr,"Usage: %s <input file> <out file>\\n",argv[0]);
        exit(0);
    
    filename =    argv[1];
    outfilename = argv[2];
    // 打开输入文件,创建格式上下文环境
    if (avformat_open_input(&fmt_ctx,filename,NULL,NULL) < 0)
        fprintf(stderr,"Could not open source file %s\\n",filename);
        exit(1);
    
    // 检索多媒体文件中的流信息
    if (avformat_find_stream_info(fmt_ctx,NULL) < 0)
        fprintf(stderr,"Could not find stream information\\n");
        exit(1);
    
    // 打印输入文件的详细信息
    av_dump_format(fmt_ctx,0,filename,0);
    av_init_packet(&avPacket);
    // 查找视频流,返回值ret是流的索引号
    ret = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_VIDEO,-1,-1,NULL,0);
    if (ret < 0)
        fprintf(stderr,"Could not find %d stream in input file '%s'\\n",
                av_get_media_type_string(AVMEDIA_TYPE_VIDEO),filename);
        return ret;
    
    // 根据索引号获取流
    stream_index = ret;
    stream = fmt_ctx->streams[stream_index];
    // 根据流中编码参数中的解码器ID查找解码器
    codec = avcodec_find_encoder(stream->codecpar->codec_id);
    if (!codec)
        fprintf(stderr,"Failed to find %s codec\\n",
                av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        return AVERROR(EINVAL);
    
    // 分配输出文件的上下文环境
    codec_context = avcodec_alloc_context3(codec);
    if (!codec_context)
        fprintf(stderr,"Could not allocate video codec context\\n");
        exit(1);
    
    // 将视频流的编解码参数直接拷贝到输出上下文环境中
    if (avcodec_parameters_to_context(codec_context,stream->codecpar) < 0)
        fprintf(stderr,"Failed to copy %s codec parameters to decoder context\\n",
                av_get_media_type_string(AVMEDIA_TYPE_VIDEO));
        return ret;
    
    // 打开解码器
    if (avcodec_open2(codec_context,codec,NULL)<0)
        fprintf(stderr,"Could not open codec\\n");
        exit(1);
    
    /*
        初始化图片转换上下文环境
        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);
        srcW/srcH/srcFormat分别为原始图像数据的宽高和数据类型,数据类型比如AV_PIX_FMT_YUV420、PAV_PIX_FMT_RGB24
        dstW/dstH/dstFormat分别为输出图像数据的宽高和数据类型
        flags是scale算法种类;SWS_BICUBIC、SWS_BICUBLIN、SWS_POINT、SWS_SINC等
        最后3个参数不用管,都设为NULL
    */
    img_convert_ctx = sws_getContext(codec_context->width,codec_context->height,
                                     codec_context->pix_fmt,
                                     codec_context->width,codec_context->height,
                                     AV_PIX_FMT_BGR24,
                                     SWS_BICUBIC,NULL,NULL,NULL);
    if (img_convert_ctx == NULL)
        fprintf(stderr,"cannot initialize the conversion context\\n");
        exit(1);
    
    frame = av_frame_alloc();
    if (!frame)
        fprintf(stderr,"Could not allocate video frame\\n");
        exit(1);
    
    // 循环从多媒体文件中读取一帧一帧的数据
    while (av_read_frame(fmt_ctx,&avPacket) >= 0)
        // 如果读取到的数据包的stream_index和视频流的stream_index一致就进行解码
        if (avPacket.stream_index == stream_index)
            if (decode_write_frame(outfilename,codec_context,img_convert_ctx,frame,
                                   &frame_count,&avPacket,0) < 0)
                exit(1);
            
        
        av_packet_unref(&avPacket);
    
    /* Some codecs, such as MPEG, transmit the I- and P-frame with a
       latency of one frame. You must do the following to have a
       chance to get the last frame of the video. */
    avPacket.data = NULL;
    avPacket.size = 0;
    decode_write_frame(outfilename,codec_context,img_convert_ctx,frame,
                       &frame_count,&avPacket,1);
    fclose(f);
    avformat_close_input(&fmt_ctx);
    sws_freeContext(img_convert_ctx);
    avcodec_free_context(&codec_context);
    av_frame_free(&frame);

    return 0;

 

以上是关于音视频处理 ffmpeg中级开发 视频转图片的主要内容,如果未能解决你的问题,请参考以下文章

FFMPEG音视频开发指南

FFMPEG音视频开发指南

ffmpeg图片转视频,图片+音频合成视频每秒一张图

《FFmpeg代码技巧》系列(中级)-总览

《FFmpeg代码技巧》系列(中级)-总览

FFmpeg安装以及视频转成图片_图片转成视频