溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點(diǎn)擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

C++?ffmpeg如何實(shí)現(xiàn)將視頻幀轉(zhuǎn)換成jpg或png等圖片

發(fā)布時(shí)間:2023-03-28 16:15:04 來源:億速云 閱讀:207 作者:iii 欄目:開發(fā)技術(shù)

本篇內(nèi)容介紹了“C++ ffmpeg如何實(shí)現(xiàn)將視頻幀轉(zhuǎn)換成jpg或png等圖片”的有關(guān)知識,在實(shí)際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

    一、如何實(shí)現(xiàn)

    1、查找編碼器

    首先需要查找圖片編碼器,比如jpg為AV_CODEC_ID_MJPEG,png為AV_CODEC_ID_PNG

    示例代碼:

    enum AVCodecID codec = avcodec_find_encoder(AV_CODEC_ID_MJPEG);

    2、構(gòu)造編碼器上下文

    有了編碼器就可以構(gòu)造編碼器上下文了。

    AVCodecContext*ctx = avcodec_alloc_context3(codec);
    ctx->bit_rate = 3000000;
    ctx->width = frame->width;//視頻幀的寬
    ctx->height = frame->height;//視頻幀的高
    ctx->time_base.num = 1;
    ctx->time_base.den = 25;
    ctx->gop_size = 10;
    ctx->max_b_frames = 0;
    ctx->thread_count = 1;
    ctx->pix_fmt = *codec->pix_fmts;//使用編碼器適配的像素格式
    //打開編碼器
    avcodec_open2(ctx, codec, NULL);

    3、像素格式轉(zhuǎn)換

    如果輸入視頻幀的像素和編碼器的像素格式不相同則需要轉(zhuǎn)換像素格式,我們采用SwsContext 轉(zhuǎn)換即可

    AVFrame*rgbFrame = av_frame_alloc();//轉(zhuǎn)換后的幀
    swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, ctx->pix_fmt, 1, NULL, NULL, NULL);
    int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1) * 2;
    buffer = (unsigned char*)av_malloc(bufferSize);
    //構(gòu)造幀的緩存
    av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, ctx->pix_fmt, frame->width, frame->height, 1);
    sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, rgbFrame->data, rgbFrame->linesize);
    //構(gòu)造必要的參數(shù)
    rgbFrame->format = ctx->pix_fmt;
    rgbFrame->width = ctx->width;
    rgbFrame->height = ctx->height;

    4、編碼

    得到轉(zhuǎn)后的幀就可以編碼

    ret = avcodec_send_frame(ctx, rgbFrame);

    5、獲取圖片數(shù)據(jù)

    獲取解碼后的包即可得到圖片數(shù)據(jù)。

    uint8_t* outbuf;//輸出圖片的緩存
    size_t outbufSize;//緩存大小
    AVPacket pkt;
    av_init_packet(&pkt);
    //獲取解碼的包
    avcodec_receive_packet(ctx, &pkt);
    //將圖片數(shù)據(jù)拷貝到緩存
    if (pkt.size > 0 && pkt.size <= outbufSize)
    memcpy(outbuf, pkt.data, pkt.size);

    6、銷毀資源

    將上述步驟使用的對象銷毀。

    if (swsContext)
    {
        sws_freeContext(swsContext);
    }
    if (rgbFrame)
    {
        av_frame_unref(rgbFrame);
        av_frame_free(&rgbFrame);
    }
    if (buffer)
    {
        av_free(buffer);
    }
    av_packet_unref(&pkt);
    if (ctx)
    {
        avcodec_close(ctx);
        avcodec_free_context(&ctx);
    }

    二、完整代碼

    /// <summary>
    /// 幀轉(zhuǎn)圖片
    /// 如果外部提供的緩存長度不足則不會寫入。
    /// </summary>
    /// <param name="frame">[in]視頻幀</param>
    /// <param name="codecID">[in]圖片編碼器ID,如jpg:AV_CODEC_ID_MJPEG,png:AV_CODEC_ID_PNG</param>
    /// <param name="outbuf">[out]圖片緩存,由外部提供</param>
    /// <param name="outbufSize">[in]圖片緩存長度</param>
    /// <returns>返回圖片實(shí)際長度</returns>
    static int frameToImage(AVFrame* frame, enum AVCodecID codecID, uint8_t* outbuf, size_t outbufSize)
    {
        int ret = 0;
        AVPacket pkt;
        AVCodec* codec;
        AVCodecContext* ctx = NULL;
        AVFrame* rgbFrame = NULL;
        uint8_t* buffer = NULL;
        struct SwsContext* swsContext = NULL;
        av_init_packet(&pkt);
        codec = avcodec_find_encoder(codecID);
        if (!codec)
        {
            printf("avcodec_send_frame error %d", codecID);
            goto end;
        }
        if (!codec->pix_fmts)
        {
            printf("unsupport pix format with codec %s", codec->name);
            goto end;
        }
        ctx = avcodec_alloc_context3(codec);
        ctx->bit_rate = 3000000;
        ctx->width = frame->width;
        ctx->height = frame->height;
        ctx->time_base.num = 1;
        ctx->time_base.den = 25;
        ctx->gop_size = 10;
        ctx->max_b_frames = 0;
        ctx->thread_count = 1;
        ctx->pix_fmt = *codec->pix_fmts;
        ret = avcodec_open2(ctx, codec, NULL);
        if (ret < 0)
        {
            printf("avcodec_open2 error %d", ret);
            goto end;
        }
        if (frame->format != ctx->pix_fmt)
        {
            rgbFrame = av_frame_alloc();
            if (rgbFrame == NULL)
            {
                printf("av_frame_alloc  fail:%d");
                goto end;
            }
            swsContext = sws_getContext(frame->width, frame->height, (enum AVPixelFormat)frame->format, frame->width, frame->height, ctx->pix_fmt, 1, NULL, NULL, NULL);
            if (!swsContext)
            {
                printf("sws_getContext  fail:%d");
                goto end;
            }
            int bufferSize = av_image_get_buffer_size(ctx->pix_fmt, frame->width, frame->height, 1) * 2;
            buffer = (unsigned char*)av_malloc(bufferSize);
            if (buffer == NULL)
            {
                printf("buffer alloc fail:%d", bufferSize);
                goto end;
            }
            av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, buffer, ctx->pix_fmt, frame->width, frame->height, 1);
            if ((ret = sws_scale(swsContext, frame->data, frame->linesize, 0, frame->height, rgbFrame->data, rgbFrame->linesize)) < 0)
            {
                printf("sws_scale error %d", ret);
            }
            rgbFrame->format = ctx->pix_fmt;
            rgbFrame->width = ctx->width;
            rgbFrame->height = ctx->height;
            ret = avcodec_send_frame(ctx, rgbFrame);
        }
        else
        {
            ret = avcodec_send_frame(ctx, frame);
        }
        if (ret < 0)
        {
            printf("avcodec_send_frame error %d", ret);
            goto end;
        }
        ret = avcodec_receive_packet(ctx, &pkt);
        if (ret < 0)
        {
            printf("avcodec_receive_packet error %d", ret);
            goto end;
        }
        if (pkt.size > 0 && pkt.size <= outbufSize)
            memcpy(outbuf, pkt.data, pkt.size);
        ret = pkt.size;
    end:
        if (swsContext)
        {
            sws_freeContext(swsContext);
        }
        if (rgbFrame)
        {
            av_frame_unref(rgbFrame);
            av_frame_free(&rgbFrame);
        }
        if (buffer)
        {
            av_free(buffer);
        }
        av_packet_unref(&pkt);
        if (ctx)
        {
            avcodec_close(ctx);
            avcodec_free_context(&ctx);
        }
        return ret;
    }

    三、使用示例

    1、截取視頻幀并保存文件

    void main() {
        AVFrame* frame;//視頻解碼得到的幀
        saveFrameToJpg(frame,"snapshot.jpg");
    }
    /// <summary>
    /// 將視頻幀保存為jpg圖片
    /// </summary>
    /// <param name="frame">視頻幀</param>
    /// <param name="path">保存的路徑</param>
    void saveFrameToJpg(AVFrame*frame,const char*path) {
        //確保緩沖區(qū)長度大于圖片,使用brga像素格式計(jì)算。如果是bmp或tiff依然可能超出長度,需要加一個(gè)頭部長度,或直接乘以2。
        int bufSize = av_image_get_buffer_size(AV_PIX_FMT_BGRA, frame->width, frame->height, 64);
        //申請緩沖區(qū)
        uint8_t* buf = (uint8_t*)av_malloc(bufSize);
        //將視頻幀轉(zhuǎn)換成圖片
        int picSize = frameToImage(frame, AV_CODEC_ID_MJPEG, buf, bufSize);
        //寫入文件
        auto f = fopen(path, "wb+");
        if (f)
        {
            fwrite(buf, sizeof(uint8_t), bufSize, f);
            fclose(f);
        }
        //釋放緩沖區(qū)
        av_free(buf);
    }

    2、自定義數(shù)據(jù)構(gòu)造AVFrame

    void main() {
        uint8_t*frameData;//解碼得到的視頻數(shù)據(jù)
        AVFrame* frame=allocFrame(frameData,640,360,AV_PIX_FMT_YUV420P);
        saveFrameToJpg(frame,"snapshot.jpg");//此方法定義在示例1中
        av_frame_free(&frame);
    }
    /// <summary>
    /// 通過裸數(shù)據(jù)生成avframe
    /// </summary>
    /// <param name="frameData">幀數(shù)據(jù)</param>
    /// <param name="width">幀寬</param>
    /// <param name="height">幀高</param>
    /// <param name="format">像素格式</param>
    /// <returns>avframe,使用完成后需要調(diào)用av_frame_free釋放</returns>
    AVFrame* allocFrame(uint8_t*frameData,int width,int height,AVPixelFormat format) {
        AVFrame* frame = av_frame_alloc();
        frame->width = width;
        frame->height = height;
        frame->format = format;
        av_image_fill_arrays(frame->data, frame->linesize, frameData, format, frame->width, frame->height, 64);
        return frame;
    }

    “C++ ffmpeg如何實(shí)現(xiàn)將視頻幀轉(zhuǎn)換成jpg或png等圖片”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!

    向AI問一下細(xì)節(jié)

    免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

    AI