DoubleLi

qq: 517712484 wx: ldbgliet

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

索引地址:系列教程索引地址

上一篇:FFmpeg4入门系列教程5:解码视频流过程

上一篇介绍了解码的基本流程,获取了视频帧数,但是没有视频每一帧数据的解码操作。

我们从视频中取出每一帧进行操作,我们已经分配了AVFrame内存,当我们转换它颜色空间时仍然需要一个位置来放置原始数据。我们使用av_image_get_buffer_size来获得我们需要的大小,并手动分配空间:

//一帧图像数据大小
int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height, 1);
unsigned char *out_buffer = (unsigned char *)av_malloc(numBytes * sizeof(unsigned char));
C

av_malloc()是ffmpeg的malloc,它只是malloc的一个简单包装器,它确保内存地址是对齐的。但是它不会保护您免受内存泄漏、双重释放或其他malloc问题的影响。现在我们使用av_image_fill_arrays()将AVFrame与新分配的缓冲区关联起来。关于AVPicture的转换:AVPicture结构是AVFrame结构的子集-AVFrame结构的开头与AVPicture结构相同。

//会将pFrameRGB的数据按RGB格式自动"关联"到buffer,即pFrameRGB中的数据改变了,out_buffer中的数据也会相应的改变
av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, out_buffer, AV_PIX_FMT_RGB32,codecCtx->width, codecCtx->height,1);
C

我们从out_buffer中取我们想要的数据,但是还需要有将yuv格式转换为RGB格式的操作。

//================================ 设置数据转换参数 ================================//
struct SwsContext *img_ctx = sws_getContext(
        codecCtx->width, codecCtx->height, codecCtx->pix_fmt, //源地址长宽以及数据格式
        codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB32,  //目的地址长宽以及数据格式
        SWS_BICUBIC, NULL, NULL, NULL); //算法类型  AV_PIX_FMT_YUVJ420P   AV_PIX_FMT_BGR24
C

首先构建一个源和目标图片的格式、长宽的对应关系。然后将对象的转换关系写明:

sws_scale(img_ctx, yuvFrame->data, yuvFrame->linesize, 0, codecCtx->height,rgbFrame->data, rgbFrame->linesize);
C

接下来进行帧解码并且保存为图片。

解码流程图为:

flow

函数调用流程图为:

flow

完整测试代码(根据官方代码和上一篇代码修改):

#include <stdio.h>
#include <stdlib.h>
#include "libavcodec/avcodec.h"
#include "libavfilter/avfilter.h"
#include "libavformat/avformat.h"
#include "libavutil/avutil.h"
#include "libavutil/ffversion.h"
#include "libswresample/swresample.h"
#include "libswscale/swscale.h"
#include "libpostproc/postprocess.h"

//将FFmpeg解码后的数据保存到本地文件
void saveFrame(AVFrame *pFrame, int width, int height, int iFrame)
{
    FILE *pFile;
    char szFilename[32];
    int y;

    // 打开文件
    sprintf(szFilename, "frame%d.ppm", iFrame);
    pFile = fopen(szFilename, "wb");
    if (pFile == NULL)
        return;

    // 写入文件头
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);

    // 写入像素数据
    for (y = 0; y < height; y++)
        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);

    // 关闭文件
    fclose(pFile);
}

int main() {
    char filePath[]       = "/home/jackey/Videos/Sample.mkv";//文件地址
    int  videoStreamIndex = -1;//视频流所在流序列中的索引
    int ret=0;//默认返回值

    //需要的变量名并初始化
    AVFormatContext *fmtCtx=NULL;
    AVPacket *pkt =NULL;
    AVCodecContext *codecCtx=NULL;
    AVCodecParameters *avCodecPara=NULL;
    AVCodec *codec=NULL;
    AVFrame *yuvFrame = av_frame_alloc();
    AVFrame *rgbFrame = av_frame_alloc();

    do{
        //=========================== 创建AVFormatContext结构体 ===============================//
        //分配一个AVFormatContext,FFMPEG所有的操作都要通过这个AVFormatContext来进行
        fmtCtx = avformat_alloc_context();
        //==================================== 打开文件 ======================================//
        if ((ret=avformat_open_input(&fmtCtx, filePath, NULL, NULL)) != 0) {
            printf("cannot open video file\n");
            break;
        }

        //=================================== 获取视频流信息 ===================================//
        if ((ret=avformat_find_stream_info(fmtCtx, NULL)) < 0) {
            printf("cannot retrive video info\n");
            break;
        }

        //循环查找视频中包含的流信息,直到找到视频类型的流
        //便将其记录下来 保存到videoStreamIndex变量中
        for (unsigned int i = 0; i < fmtCtx->nb_streams; i++) {
            if (fmtCtx->streams[ i ]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
                videoStreamIndex = i;
                break;//找到视频流就退出
            }
        }

        //如果videoStream为-1 说明没有找到视频流
        if (videoStreamIndex == -1) {
            printf("cannot find video stream\n");
            break;
        }

        //打印输入和输出信息:长度 比特率 流格式等
        av_dump_format(fmtCtx, 0, filePath, 0);

        //=================================  查找解码器 ===================================//
        avCodecPara = fmtCtx->streams[ videoStreamIndex ]->codecpar;
        codec       = avcodec_find_decoder(avCodecPara->codec_id);
        if (codec == NULL) {
            printf("cannot find decoder\n");
            break;
        }
        //根据解码器参数来创建解码器内容
        codecCtx = avcodec_alloc_context3(codec);
        avcodec_parameters_to_context(codecCtx, avCodecPara);
        if (codecCtx == NULL) {
            printf("Cannot alloc context.");
            break;
        }

        //================================  打开解码器 ===================================//
        if ((ret=avcodec_open2(codecCtx, codec, NULL)) < 0) { // 具体采用什么解码器ffmpeg经过封装 我们无须知道
            printf("cannot open decoder\n");
            break;
        }

        //================================ 设置数据转换参数 ================================//
        struct SwsContext *img_ctx = sws_getContext(
            codecCtx->width, codecCtx->height, codecCtx->pix_fmt, //源地址长宽以及数据格式
            codecCtx->width, codecCtx->height, AV_PIX_FMT_RGB32,  //目的地址长宽以及数据格式
            SWS_BICUBIC, NULL, NULL, NULL);                       //算法类型  AV_PIX_FMT_YUVJ420P   AV_PIX_FMT_BGR24

        //==================================== 分配空间 ==================================//
        //一帧图像数据大小
        int numBytes = av_image_get_buffer_size(AV_PIX_FMT_RGB32, codecCtx->width, codecCtx->height, 1);
        unsigned char *out_buffer = (unsigned char *)av_malloc(numBytes * sizeof(unsigned char));


        //=========================== 分配AVPacket结构体 ===============================//
        int       i   = 0;//用于帧计数
        pkt = av_packet_alloc();                      //分配一个packet
        av_new_packet(pkt, codecCtx->width * codecCtx->height); //调整packet的数据

        //会将pFrameRGB的数据按RGB格式自动"关联"到buffer  即pFrameRGB中的数据改变了
        //out_buffer中的数据也会相应的改变
        av_image_fill_arrays(rgbFrame->data, rgbFrame->linesize, out_buffer, AV_PIX_FMT_RGB32,
                             codecCtx->width, codecCtx->height, 1);

        //===========================  读取视频信息 ===============================//
        while (av_read_frame(fmtCtx, pkt) >= 0) { //读取的是一帧视频  数据存入一个AVPacket的结构中
            if (pkt->stream_index == videoStreamIndex){
                if (avcodec_send_packet(codecCtx, pkt) == 0){
                    while (avcodec_receive_frame(codecCtx, yuvFrame) == 0){
                        if (++i <= 500 && i >= 455){
                            sws_scale(img_ctx,
                                      (const uint8_t* const*)yuvFrame->data,
                                      yuvFrame->linesize,
                                      0,
                                      codecCtx->height,
                                      rgbFrame->data,
                                      rgbFrame->linesize);
                            saveFrame(rgbFrame, codecCtx->width, codecCtx->height, i);
                        }
                    }
                }
            }
            av_packet_unref(pkt);//重置pkt的内容
        }
        printf("There are %d frames int total.\n", i);
    }while(0);
    //===========================释放所有指针===============================//
    av_packet_free(&pkt);
    avcodec_close(codecCtx);
    avcodec_parameters_free(&avCodecPara);
    avformat_close_input(&fmtCtx);
    avformat_free_context(fmtCtx);
    av_frame_free(&yuvFrame);
    av_frame_free(&rgbFrame);

    return ret;
}
C

原图(视频中的某一帧,近似)是:

decode

保存的ppm图片是:

decode

离远看差不多。

GitHub项目地址(源代码):ffmpeg_Beginner中的6.video_decode_save

下一篇:FFmpeg4入门系列教程7:解码视频并保存为YUV格式文件

from:https://feater.top/series/ffmpeg/1122/

posted on 2021-07-21 17:00  DoubleLi  阅读(451)  评论(0编辑  收藏  举报