在CB2010中调用ffmpeg(5)

  1. 视频编码

今天的目的在于将视频文件中的帧取出来,然后保存。

可以在上一次的基础上进行。最终代码如下。

void __fastcall TFFMPEGTestForm::Button1Click(TObject *Sender) { // 取得文件信息

    const char *fileName = "d:\test.mp4";

    AVFormatContext * pFormatCtx = NULL;

    // 读取文件的头部并且把信息保存到AVFormatContext结构体中

    int err = avformat_open_input(&pFormatCtx, fileName, NULL, 0);

    if (err != 0)

        return;

    err = av_find_stream_info(pFormatCtx); // Retrieve stream information

    if (err < 0)

        return;

    // FormatCtx->streams是一组大小为pFormatCtx->nb_streams的指针

    for (uint32_t i = 0; i < pFormatCtx->nb_streams; i++) {

        // stream 结构数据

        AVStream *pStream = pFormatCtx->streams[i];

        // 帧率信息

        AVRational frameRate = pStream->r_frame_rate;

        // 时间单位比率

        AVRational timeBase = pStream->time_base;

        // stream duration

        int64_t duration = pStream->duration;

 

        // 获取Codec数据结构

        AVCodecContext *pCodecCtx = pStream->codec;

        AVMediaType codecType = pCodecCtx->codec_type;

 

        AVCodecID codecId = pCodecCtx->codec_id;

        // enum AVCodecID codec_id; /* see AV_CODEC_ID_xxx */

 

        if (codecType == AVMEDIA_TYPE_VIDEO) {

            // 获取Video基本信息

            int width = pCodecCtx->width;

            int height = pCodecCtx->height;

            PixelFormat pixelFormat = pCodecCtx->pix_fmt;

        }

        else if (codecType == AVMEDIA_TYPE_AUDIO) {

            // 获取Audio基本信息

            int channels = pCodecCtx->channels;

            int sample_rate = pCodecCtx->sample_rate;

            AVSampleFormat sampleFmt = pCodecCtx->sample_fmt;

        }

        // Find the decoder for the video stream

        AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id);

        if (pCodec == NULL)

            av_log(NULL, AV_LOG_ERROR, "Unsupported codec! ");

        else if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0)

            av_log(NULL, AV_LOG_ERROR, "Could not open codec! ");

        else {

            AVFrame * pFrame = avcodec_alloc_frame();

            AVFrame * pFrameRGB = avcodec_alloc_frame();

            if (pFrameRGB == NULL)

                av_log(NULL, AV_LOG_ERROR, "alloc frame failure! ");

            else {

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

                uint8_t * buffer = (uint8_t*)av_malloc(numBytes*sizeof(uint8_t));

                avpicture_fill((AVPicture*)pFrameRGB, buffer, PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height);

                int frameFinished;

                AVPacket packet;

                i = 0;

                while (1) {

                    if (av_read_frame(pFormatCtx, &packet) < 0) // 创建AVPacket,填充其data数据缓冲区

                        break;

                    // while (av_read_frame(pFormatCtx, &packet) >= 0) {

                    // Is this a packet from the video stream?

                    if (packet.stream_index == video_stream_index) {

                        // Decode video frame

                        frameFinished = 0;

                        avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);

                        // avcodec_decode_video(pCodecCtx, pFrame, &frameFinished, packet.data, packet.size);

                        // Did we get a video frame?

                        if (frameFinished) {

                            // Convert the image from its native format to RGB

                            img_convert((AVPicture*)pFrameRGB, PIX_FMT_RGB24, (AVPicture*)pFrame, pCodecCtx->pix_fmt,

                                pCodecCtx->width, pCodecCtx->height);

                            if (++i % 100 == 1)

                                SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);

                        }

                    }

                }

                // Free the packet that was allocated by av_read_frame

                av_free_packet(&packet);

                av_free(buffer);

            }

            av_free(pFrameRGB);

            av_free(pFrame);

        }

        avcodec_close(pCodecCtx);

    }

    // 释放

    if (pFormatCtx != NULL) {

        av_close_input_file(pFormatCtx);

        pFormatCtx = NULL;

    }

}

上面代码编译时,会提示img_convert找不到。

原因在于早期的ffmpeg库中,采用img_convert图像格式转换,而较新的ffmpeg库中去掉了该函数,加入了swscale库,用该库中的sws_scale函数可以替代img_convert,两个函数原型如下:

int img_convert(AVPicture *dst, int dst_pix_fmt,

                const AVPicture *src, int src_pix_fmt,

                int src_width, int src_height)

int sws_scale(struct SwsContext *ctx, uint8_t* src[], int srcStride[],

                    int srcSliceY, int srcSliceH, uint8_t* dst[], int dstStride[])

为了方便,可以自己写一个img_convert函数,然后函数内部用sws_scale来实现,只是对于一些错误的处理及返回值处理不太严格,但基本能用,代码如下:

int img_convert(AVPicture *dst, int dst_pix_fmt,

                const AVPicture *src, int src_pix_fmt,

                int src_width, int src_height)

{

    int w;

    int h;

    SwsContext *pSwsCtx;

    w = src_width;

    h = src_height;

    pSwsCtx = sws_getContext(w, h, src_pix_fmt, 

                            w, h, dst_pix_fmt,

                            SWS_BICUBIC, NULL, NULL, NULL);

    sws_scale(pSwsCtx, src->data, src->linesize,

            0, h, dst->data, dst->linesize);

   

     

 //这里释放掉pSwsCtx的内存

sws_freeContext(pSwsCtx);
 

    return 0;

}

 

其实,稍微多用一下代码就知道,这个ffmpeg感觉与GDI一样,需要完成一些准备工作,然后按相应的流程进行处理,最后释放。

应该可以设计成为一个类库,这样只管用。

这项工作可以留待以后,把这个东东搞熟了再说。

运行结果,生成了一堆PPM文件

这些PPM文件可以用XnView程序查看。

这种结果,确实很爽的。现在的成果,可以取得视频文件的任意一帧,并保存为文件。

原文地址:https://www.cnblogs.com/drgraph/p/3612862.html