FFmpeg 像素格式转换与图像缩放实战

在ffmpeg中如果需要修改图像的像素格式,或者修改图像的宽高大小,最简单的就是使用sws_scale函数进行转换!

先前学习了ffmpeg的 编码解码 操作,基于此,再来了解一下像素格式的转换!

调用流程:

cpp 复制代码
sws_getContext()  →  初始化转换上下文(只调用一次)
       ↓
sws_scale()       →  执行实际转换(每帧调用)
       ↓
sws_freeContext() →  释放上下文(结束时调用)

运用流程:

cpp 复制代码
    解析出一帧              发送解码                接收解码            像素格式转换
av_parser_parse2() → avcodec_send_packet() → avcodec_receive_frame() → sws_scale()

1. sws_getContext

sws_getContext 是 FFmpeg 的 libswscale 库中用于初始化图像转换上下文的核心函数。它的主要任务是创建一个 SwsContext 结构体。

①函数原型

cpp 复制代码
struct SwsContext *sws_getContext(
    int srcW,                   // 源图像宽度
    int srcH,                   // 源图像高度
    enum AVPixelFormat srcFormat, // 源像素格式
    int dstW,                   // 目标宽度
    int dstH,                   // 目标高度
    enum AVPixelFormat dstFormat, // 目标像素格式
    int flags,                  // 缩放算法标志
    SwsFilter *srcFilter,       // 源滤波器(通常 NULL)
    SwsFilter *dstFilter,       // 目标滤波器(通常 NULL)
    const double *param         // 算法参数(通常 NULL)
);

②参数说明

参数 类型 说明
srcW int 源图像宽度(像素)
srcH int 源图像高度(像素)
srcFormat enum AVPixelFormat 源像素格式(如 AV_PIX_FMT_YUV420P、AV_PIX_FMT_RGB24)
dstW int 目标图像宽度(像素)
dstH int 目标图像高度(像素)
dstFormat enum AVPixelFormat 目标像素格式(如 AV_PIX_FMT_YUV420P、AV_PIX_FMT_RGB24)
flags int 缩放算法标志(如 SWS_BILINEAR、SWS_BICUBIC、SWS_LANCZOS)
srcFilter SwsFilter * 源滤波器,一般不用,填 NULL
dstFilter SwsFilter * 目标滤波器,一般不用,填 NULL
param const double * 算法参数,一般不用,填 NULL

常用 flags 算法

标志 说明 速度/质量
SWS_FAST_BILINEAR 快速双线性 快,质量尚可
SWS_BILINEAR 双线性 中等
SWS_BICUBIC 双三次 较慢,质量好
SWS_LANCZOS Lanczos 慢,质量最好
SWS_POINT 最近邻 最快,缩放时用
SWS_AREA 面积法缩小 缩小图片推荐

③返回值

  • 成功:返回一个指向 SwsContext 结构体的指针
  • 失败:返回 NULL

④使用示例

cpp 复制代码
// yuv420转rgb24
SwsContext *sws_ctx = sws_getContext(800, 600, AV_PIX_FMT_YUV420P,          // 源格式
                                     640, 480, AV_PIX_FMT_RGB24,            // 目标格式
                                     SWS_BILINEAR, NULL, NULL, NULL);       // SWS_BILINEAR 双线性插值算法
if (!sws_ctx) {
    std::cout << "sws_getContext failed!" << std::endl;
    return -1;
}

2. sws_scale

使用 sws_getContext 创建的上下文,将源图像数据转换为目标图像数据。

①函数原型

cpp 复制代码
int sws_scale(
    struct SwsContext *c,               // 上下文
    const uint8_t *const srcSlice[],   // 源数据指针数组
    const int srcStride[],             // 源每行字节数(linesize)
    int srcSliceY,                     // 从第几行开始处理
    int srcSliceH,                     // 处理多少行
    uint8_t *const dst[],              // 目标数据指针数组
    const int dstStride[]              // 目标每行字节数
);

②参数说明

参数 类型 说明
c SwsContext * 由 sws_getContext 创建的转换上下文
srcSlice\[\] const uint8_t *const \[\] 源图像各平面数据指针数组(对应 frame->data\[\])
srcStride\[\] const int \[\] 源各平面每行字节数(对应 frame->linesize\[\],注意是字节不是像素)
srcSliceY int 从源图像的第几行开始处理(通常填 0)
srcSliceH int 从源图像处理多少行(通常填 srcH,即全部)
dst\[\] uint8_t *const \[\] 目标图像各平面数据指针数组(对应 dst_frame->data\[\])
dstStride\[\] const int \[\] 目标各平面每行字节数(对应 dst_frame->linesize\[\])

③返回值

  • 成功:返回输出的行数
  • 失败:返回一个负数错误码

④使用示例 目标接收的frame_rgb24,需要由使用者分配内存;

cpp 复制代码
// 定义并且分配内存,用于接收转换后的一帧图像
AVFrame *frame_rgb24 = av_frame_alloc();
frame_rgb24->width = 640;
frame_rgb24->height = 480;
frame_rgb24->format = AV_PIX_FMT_RGB24;
av_frame_get_buffer(frame_rgb24, 32);		// 分配内存
cpp 复制代码
// 像素格式转换
int height = sws_scale(sws_ctx,                          // 由 sws_getContext 创建的 SwsContext 指针
                       frame->data,                      // 源图像数据指针数组
                       frame->linesize,                  // 源图像每行的字节数
                       0,                                // 处理起始行,通常为 0,表示从第一行开始
                       frame->height,                    // 要处理的行数,通常为源图像的高度
                       frame_rgb24->data,                // 目标图像数据指针数组
                       frame_rgb24->linesize             // 目标图像每行的字节数
             );
if (0 > height) {
    std::cout << "sws_scale failed! ret = " << ret << std::endl;
    goto FAILED;
}

3. 编码流程

3.1 打开视频文件

cpp 复制代码
std::ifstream ifs;
ifs.open("800_600_25.h264", std::ios::binary);
if (!ifs.is_open()) {
    std::cout << "ifs open file failed!\n";
    std::cout << "Error code: " << errno << std::endl;
    std::cout << "Error message: " << strerror(errno) << std::endl;
    return -1;
}

3.2 初始化解码器

cpp 复制代码
// 查找解码器
const AVCodec *decodec = avcodec_find_decoder(AV_CODEC_ID_H264);
if (!decodec) {
    std::cout << "avcodec_find_decoder failed!" << std::endl;
    return -1;
}

// 创建解码器上下文
AVCodecContext *decodec_ctx = avcodec_alloc_context3(decodec);
if (!decodec_ctx) {
    std::cout << "avcodec_alloc_context3 failed!" << std::endl;
    return -1;
}

// 打开解码器上下文
int ret = avcodec_open2(decodec_ctx, decodec, 0);
if (0 != ret) {
    std::cout << "avcodec_open2 failed!" << std::endl;
    PrintErr(ret);
    return -1;
}

3.3 初始化音视频码流解析器

cpp 复制代码
AVCodecParserContext *parser_ctx = av_parser_init(AV_CODEC_ID_H264);
if (!parser_ctx) {
    std::cout << "av_parser_init failed!" << std::endl;
    return -1;
}

3.4 初始化SDL部分

cpp 复制代码
SDL_Init(SDL_INIT_VIDEO);
// 创建窗口
SDL_Window *sdl_win = SDL_CreateWindow("xxxxxxxx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                       640, 480, SDL_WINDOW_OPENGL);
// 创建渲染器
SDL_Renderer *render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_TARGETTEXTURE);
// 创建 纹理|材质
SDL_Texture *texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING,
                                         640, 480);
// 设置线性插值(抗锯齿)
SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);

3.5 初始化图像转换上下文

cpp 复制代码
SwsContext *sws_ctx = sws_getContext(800, 600, AV_PIX_FMT_YUV420P,          // 源格式
                                     640, 480, AV_PIX_FMT_RGB24,            // 目标格式
                                     SWS_BILINEAR, NULL, NULL, NULL);       // SWS_BILINEAR 双线性插值算法
if (!sws_ctx) {
    std::cout << "sws_getContext failed! ret = " << ret << std::endl;
    PrintErr(ret);
    return -1;
}

3.6 读取文件数据

cpp 复制代码
while (1) {
    // 读取文件数据
    ifs.read((char *)buf, sizeof(buf));
    size_in = ifs.gcount();
    // 读取失败或读取完毕
    if (0 >= size_in) {
        std::cout << "ifs.read end!" << std::endl;
        break;
    }
	
	// other code ...
}

3.7 解析音视频裸流数据

cpp 复制代码
data_in = buf;
while (0 < size_in) {
    // 解析音视频裸流数据,从中分离出一帧一帧的压缩编码数据
    int re = av_parser_parse2(parser_ctx, decodec_ctx, &pkt->data, &pkt->size,
                         data_in, size_in, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

    data_in += re;  // 处理了多少,指针移位多少
    size_in -= re;  // 处理了多少,减去多少,小于等于0,则说明读取的字节已经处理完毕


    if (pkt->size) {
		// other code ...
	}
}

3.8 解码

cpp 复制代码
// 发送解码
ret = avcodec_send_packet(decodec_ctx, pkt);
if (0 != ret) {
    std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
    PrintErr(ret);
    goto FAILED;
}

while (0 <= ret) {
    // 接收解码
    ret = avcodec_receive_frame(decodec_ctx, frame);
    if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
        av_packet_unref(pkt);
        break;
    }
    if (0 > ret) {
        std::cout << "avcodec_receive_frame failed! ret = " << ret << std::endl;
        PrintErr(ret);
        av_packet_unref(pkt);
        goto FAILED;
    }

	// other code ...
}

3.9 像素格式转换

cpp 复制代码
int height = sws_scale(sws_ctx,                          // 由 sws_getContext 创建的 SwsContext 指针
                       frame->data,                      // 源图像数据指针数组
                       frame->linesize,                  // 源图像每行的字节数
                       0,                                // 处理起始行,通常为 0,表示从第一行开始
                       frame->height,                    // 要处理的行数,通常为源图像的高度
                       frame_rgb24->data,                // 目标图像数据指针数组
                       frame_rgb24->linesize             // 目标图像每行的字节数
             );
if (0 > height) {
    std::cout << "sws_scale failed! ret = " << ret << std::endl;
    goto FAILED;
}

frame_rgb24->pts = pts++;

3.10 SDL渲染

cpp 复制代码
SDL_UpdateTexture(texture, NULL, frame_rgb24->data[0], frame_rgb24->linesize[0]);
SDL_RenderClear(render);
SDL_RenderCopy(render, texture, NULL, NULL);
SDL_RenderPresent(render);

3.11 冲刷解码器

cpp 复制代码
ret = avcodec_send_packet(decodec_ctx, NULL);
if (0 != ret) {
    std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
    PrintErr(ret);
    goto FAILED;
}
while (0 <= ret) {
    ret = avcodec_receive_frame(decodec_ctx, frame);
    if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
        break;
    }
    if (0 > ret) {
        std::cout << "avcodec_receive_packet failed! ret = " << ret << std::endl;
        PrintErr(ret);
        goto FAILED;
    }

    // 像素格式转换
    sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, frame_rgb24->data, frame_rgb24->linesize);
    frame_rgb24->pts = pts++;

    std::cout << pts << "- ";

    // 渲染rgb24类型
    SDL_UpdateTexture(texture, NULL, frame_rgb24->data[0], frame_rgb24->linesize[0]);
    SDL_RenderClear(render);
    SDL_RenderCopy(render, texture, NULL, NULL);
    SDL_RenderPresent(render);
}

3.12 内存回收

cpp 复制代码
FAILED:
    // 内存回收
    ifs.close();
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&decodec_ctx);
    av_parser_close(parser_ctx);
    av_frame_free(&frame_rgb24);
    sws_free_context(&sws_ctx);

    if (texture) {
        SDL_DestroyTexture(texture);
    }
    if (render) {
        SDL_DestroyRenderer(render);
    }
    if (sdl_win) {
        SDL_DestroyWindow(sdl_win);
    }
    SDL_Quit();

4. 代码合集

读取本地h.264视频文件,解码后,由800x600,YUV420P转换为640x480,RGB24,再通过SDL2渲染;

cpp 复制代码
#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
}

#include "SDL.h"



// 打印错误信息
static void PrintErr(int ret) {
    char buf[1024] = { 0 };
    av_strerror(ret, buf, sizeof(buf));
    std::cout << buf << std::endl;
}


#undef main
int main(void)
{
    std::cout << av_version_info() << std::endl;


    // 打开视频文件
    std::ifstream ifs;
    ifs.open("800_600_25.h264", std::ios::binary);
    if (!ifs.is_open()) {
        std::cout << "ifs open file failed!\n";
        std::cout << "Error code: " << errno << std::endl;
        std::cout << "Error message: " << strerror(errno) << std::endl;
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化解码器

    // 查找解码器
    const AVCodec *decodec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!decodec) {
        std::cout << "avcodec_find_decoder failed!" << std::endl;
        return -1;
    }

    // 创建解码器上下文
    AVCodecContext *decodec_ctx = avcodec_alloc_context3(decodec);
    if (!decodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        return -1;
    }

    // 打开解码器上下文
    int ret = avcodec_open2(decodec_ctx, decodec, 0);
    if (0 != ret) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(ret);
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////



    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化音视频码流解析器

    AVCodecParserContext *parser_ctx = av_parser_init(AV_CODEC_ID_H264);
    if (!parser_ctx) {
        std::cout << "av_parser_init failed!" << std::endl;
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////



    // 初始化SDL部分
    SDL_Init(SDL_INIT_VIDEO);
    // 创建窗口
    SDL_Window *sdl_win = SDL_CreateWindow("xxxxxxxx", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
                                           640, 480, SDL_WINDOW_OPENGL);
    // 创建渲染器
    SDL_Renderer *render = SDL_CreateRenderer(sdl_win, -1, SDL_RENDERER_TARGETTEXTURE);
    // 创建 纹理|材质
    SDL_Texture *texture = SDL_CreateTexture(render, SDL_PIXELFORMAT_RGB24, SDL_TEXTUREACCESS_STREAMING,
                                             640, 480);
    // 设置线性插值(抗锯齿)
    SDL_SetTextureScaleMode(texture, SDL_ScaleModeLinear);





    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化图像转换上下文

    // yuv420转rgb24
    SwsContext *sws_ctx = sws_getContext(800, 600, AV_PIX_FMT_YUV420P,          // 源格式
                                         640, 480, AV_PIX_FMT_RGB24,            // 目标格式
                                         SWS_BILINEAR, NULL, NULL, NULL);       // SWS_BILINEAR 双线性插值算法
    if (!sws_ctx) {
        std::cout << "sws_getContext failed! ret = " << ret << std::endl;
        PrintErr(ret);
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////



    int pts = 0;
    unsigned char buf[4096] = { 0 };
    unsigned char *data_in = nullptr;
    int size_in = 0;
    AVPacket *pkt = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();

    // 定义并且分配内存,用于接收转换后的一帧图像
    AVFrame *frame_rgb24 = av_frame_alloc();
    frame_rgb24->width = 640;                       // encodec_ctx->width
    frame_rgb24->height = 480;                      // encodec_ctx->height
    frame_rgb24->format = AV_PIX_FMT_RGB24;       // encodec_ctx->pix_fmt
    av_frame_get_buffer(frame_rgb24, 32);

    while (1) {
        // 读取文件数据
        ifs.read((char *)buf, sizeof(buf));
        size_in = ifs.gcount();
        // 读取失败或读取完毕
        if (0 >= size_in) {
            std::cout << "ifs.read end!" << std::endl;
            break;
        }

        data_in = buf;
        while (0 < size_in) {
            // 解析音视频裸流数据,从中分离出一帧一帧的压缩编码数据
            int re = av_parser_parse2(parser_ctx, decodec_ctx, &pkt->data, &pkt->size,
                                 data_in, size_in, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

            data_in += re;  // 处理了多少,指针移位多少
            size_in -= re;  // 处理了多少,减去多少,小于等于0,则说明读取的字节已经处理完毕


            if (pkt->size) {
                // 发送解码
                ret = avcodec_send_packet(decodec_ctx, pkt);
                if (0 != ret) {
                    std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
                    PrintErr(ret);
                    goto FAILED;
                }

                while (0 <= ret) {
                    // 接收解码
                    ret = avcodec_receive_frame(decodec_ctx, frame);
                    if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
                        av_packet_unref(pkt);
                        break;
                    }
                    if (0 > ret) {
                        std::cout << "avcodec_receive_frame failed! ret = " << ret << std::endl;
                        PrintErr(ret);
                        av_packet_unref(pkt);
                        goto FAILED;
                    }
                    av_packet_unref(pkt);

                    // 像素格式转换
                    int height = sws_scale(sws_ctx,                          // 由 sws_getContext 创建的 SwsContext 指针
                                           frame->data,                      // 源图像数据指针数组
                                           frame->linesize,                  // 源图像每行的字节数
                                           0,                                // 处理起始行,通常为 0,表示从第一行开始
                                           frame->height,                    // 要处理的行数,通常为源图像的高度
                                           frame_rgb24->data,                // 目标图像数据指针数组
                                           frame_rgb24->linesize             // 目标图像每行的字节数
                                 );
                    if (0 > height) {
                        std::cout << "sws_scale failed! ret = " << ret << std::endl;
                        goto FAILED;
                    }

                    frame_rgb24->pts = pts++;
                    std::cout << pts << " ";

                    // 渲染rgb24类型
                    SDL_UpdateTexture(texture, NULL, frame_rgb24->data[0], frame_rgb24->linesize[0]);
                    SDL_RenderClear(render);
                    SDL_RenderCopy(render, texture, NULL, NULL);
                    SDL_RenderPresent(render);


                    // 睡眠 40 毫秒
                    //std::this_thread::sleep_for(std::chrono::milliseconds(40));
                }
            }
        }
    }


    // 冲刷解码器
    ret = avcodec_send_packet(decodec_ctx, NULL);
    if (0 != ret) {
        std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
        PrintErr(ret);
        goto FAILED;
    }
    while (0 <= ret) {
        ret = avcodec_receive_frame(decodec_ctx, frame);
        if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
            break;
        }
        if (0 > ret) {
            std::cout << "avcodec_receive_packet failed! ret = " << ret << std::endl;
            PrintErr(ret);
            goto FAILED;
        }

        // 像素格式转换
        sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, frame_rgb24->data, frame_rgb24->linesize);
        frame_rgb24->pts = pts++;

        std::cout << pts << "- ";

        // 渲染rgb24类型
        SDL_UpdateTexture(texture, NULL, frame_rgb24->data[0], frame_rgb24->linesize[0]);
        SDL_RenderClear(render);
        SDL_RenderCopy(render, texture, NULL, NULL);
        SDL_RenderPresent(render);
    }


FAILED:
    // 内存回收
    ifs.close();
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&decodec_ctx);
    av_parser_close(parser_ctx);
    av_frame_free(&frame_rgb24);
    sws_free_context(&sws_ctx);

    if (texture) {
        SDL_DestroyTexture(texture);
    }
    if (render) {
        SDL_DestroyRenderer(render);
    }
    if (sdl_win) {
        SDL_DestroyWindow(sdl_win);
    }
    SDL_Quit();

    return 0;
}

5. 视频效果截图

6. 进阶

ruby 复制代码
////////////////////////////////////////////////////////////////////////////////////////////////////
/// 需求:
///     1.读取800_600_25.h264视频文件;
///     2.解码;
///     3.将宽高修改为640x480,像素格式从 YUV420P 转换为 YUV444P;
///     4.编码;
///     5.写入640_480.h264文件;
////////////////////////////////////////////////////////////////////////////////////////////////////

根据 FFmpeg H264视频编码全流程解析 + FFmpeg H264视频解码全流程解析 再加上本篇学习的内容,可以完成!

6.1 实现源码

cpp 复制代码
#include <iostream>
#include <fstream>
#include <thread>
#include <chrono>

extern "C" {
#include "libavcodec/avcodec.h"
#include "libavutil/avutil.h"
#include "libswscale/swscale.h"
}

// 打印错误信息
static void PrintErr(int ret) {
    char buf[1024] = { 0 };
    av_strerror(ret, buf, sizeof(buf));
    std::cout << buf << std::endl;
}


////////////////////////////////////////////////////////////////////////////////////////////////////
/// 需求:
///     1.读取800_600_25.h264视频文件;
///     2.解码;
///     3.将宽高修改为640x480,像素格式从 YUV420P 转换为 YUV444P;
///     4.编码;
///     5.写入640_480.h264文件;
////////////////////////////////////////////////////////////////////////////////////////////////////



int main(void)
{
    std::cout << av_version_info() << std::endl;


    // 打开视频文件
    std::ifstream ifs;
    ifs.open("800_600_25.h264", std::ios::binary);
    if (!ifs.is_open()) {
        std::cout << "ifs open file failed!\n";
        std::cout << "Error code: " << errno << std::endl;
        std::cout << "Error message: " << strerror(errno) << std::endl;
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化解码器

    // 查找解码器
    const AVCodec *decodec = avcodec_find_decoder(AV_CODEC_ID_H264);
    if (!decodec) {
        std::cout << "avcodec_find_decoder failed!" << std::endl;
        return -1;
    }

    // 创建解码器上下文
    AVCodecContext *decodec_ctx = avcodec_alloc_context3(decodec);
    if (!decodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        return -1;
    }

    // 打开解码器上下文
    int ret = avcodec_open2(decodec_ctx, decodec, 0);
    if (0 != ret) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(ret);
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////



    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化音视频码流解析器

    AVCodecParserContext *parser_ctx = av_parser_init(AV_CODEC_ID_H264);
    if (!parser_ctx) {
        std::cout << "av_parser_init failed!" << std::endl;
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////



    // 打开输出文件
    std::ofstream ofd;
    ofd.open("640_480.h264", std::ios::binary);   // 一定要以二进制方式打开
    if (!ofd.is_open()) {
        std::cout << "ofd open failed!" << std::endl;
        std::cout << "Error code: " << errno << std::endl;
        std::cout << "Error message: " << strerror(errno) << std::endl;
        return -1;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化编码器

    // 查找编码器
    const AVCodec *encodec = avcodec_find_encoder(AV_CODEC_ID_H264);
    if (!encodec) {
        std::cout << "avcodec_find_encoder failed!" << std::endl;
        return 0;
    }

    // 创建编码器上下文
    AVCodecContext *encodec_ctx = avcodec_alloc_context3(encodec);
    if (!encodec_ctx) {
        std::cout << "avcodec_alloc_context3 failed!" << std::endl;
        return 0;
    }

    // 设置基础信息
    encodec_ctx->width = 640;
    encodec_ctx->height = 480;
    encodec_ctx->time_base = { 1, 25 };
    encodec_ctx->pix_fmt = AV_PIX_FMT_YUV444P;

    // 打开编码器上下文
    int re = avcodec_open2(encodec_ctx, encodec, NULL);
    if (0 != re) {
        std::cout << "avcodec_open2 failed!" << std::endl;
        PrintErr(re);
        return 0;
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////




    /////////////////////////////////////////////////////////////////////////////////////////////////
    /// 初始化图像转换上下文

    // yuv420转yuv444
    SwsContext *sws_ctx = sws_getContext(800, 600, AV_PIX_FMT_YUV420P,          // 源格式
                                         640, 480, AV_PIX_FMT_YUV444P,          // 目标格式
                                         SWS_BILINEAR, NULL, NULL, NULL);       // SWS_BILINEAR 双线性插值算法

    /////////////////////////////////////////////////////////////////////////////////////////////////



    int pts = 0;
    unsigned char buf[4096] = { 0 };
    unsigned char *data_in = nullptr;
    int size_in = 0;
    AVPacket *pkt = av_packet_alloc();
    AVFrame *frame = av_frame_alloc();

    // 定义并且分配内存,用于接收转换后的一帧图像
    AVFrame *frame_yuv444 = av_frame_alloc();
    frame_yuv444->width = 640;                       // encodec_ctx->width
    frame_yuv444->height = 480;                      // encodec_ctx->height
    frame_yuv444->format = AV_PIX_FMT_YUV444P;       // encodec_ctx->pix_fmt
    av_frame_get_buffer(frame_yuv444, 32);

    while (1) {
        // 读取文件数据
        ifs.read((char *)buf, sizeof(buf));
        size_in = ifs.gcount();
        // 读取失败或读取完毕
        if (0 >= size_in) {
            std::cout << "ifs.read end!" << std::endl;
            break;
        }

        data_in = buf;
        while (0 < size_in) {
            // 解析音视频裸流数据,从中分离出一帧一帧的压缩编码数据
            int re = av_parser_parse2(parser_ctx, decodec_ctx, &pkt->data, &pkt->size,
                                 data_in, size_in, AV_NOPTS_VALUE, AV_NOPTS_VALUE, 0);

            data_in += re;  // 处理了多少,指针移位多少
            size_in -= re;  // 处理了多少,减去多少,小于等于0,则说明读取的字节已经处理完毕


            if (pkt->size) {
                // 发送解码
                ret = avcodec_send_packet(decodec_ctx, pkt);
                if (0 != ret) {
                    std::cout << "avcodec_send_packet failed! ret = " << ret << std::endl;
                    PrintErr(ret);
                    goto FAILED;
                }

                while (0 <= ret) {
                    // 接收解码
                    ret = avcodec_receive_frame(decodec_ctx, frame);
                    if (AVERROR(EAGAIN) == ret || AVERROR_EOF == ret) {
                        av_packet_unref(pkt);
                        break;
                    }
                    if (0 > ret) {
                        std::cout << "avcodec_receive_frame failed! ret = " << ret << std::endl;
                        PrintErr(ret);
                        av_packet_unref(pkt);
                        goto FAILED;
                    }
                    av_packet_unref(pkt);

                    // 像素格式转换
                    int height = sws_scale(sws_ctx,                          // 由 sws_getContext 创建的 SwsContext 指针
                                           frame->data,                      // 源图像数据指针数组
                                           frame->linesize,                  // 源图像每行的字节数
                                           0,                                // 处理起始行,通常为 0,表示从第一行开始
                                           frame->height,                    // 要处理的行数,通常为源图像的高度
                                           frame_yuv444->data,                // 目标图像数据指针数组
                                           frame_yuv444->linesize             // 目标图像每行的字节数
                                 );
                    if (0 > height) {
                        std::cout << "sws_scale failed! ret = " << ret << std::endl;
                        goto FAILED;
                    }

                    frame_yuv444->pts = pts++;

                    // 发送编码
                    int re = avcodec_send_frame(encodec_ctx, frame_yuv444);
                    if (re < 0) {
                        std::cout << "avcodec_send_frame failed!" << std::endl;
                        PrintErr(re);
                        break;
                    }


                    while (re >= 0) {
                        // 接收编码后的数据包
                        int re = avcodec_receive_packet(encodec_ctx, pkt);
                        if (re == AVERROR(EAGAIN) || re == AVERROR_EOF) {
                            // 目前还没有数据或已经到结尾
                            av_packet_unref(pkt);
                            break;

                        } else if (re < 0) {
                            std::cout << "avcodec_receive_packet failed!" << std::endl;
                            PrintErr(re);
                            av_packet_unref(pkt);
                            return -2;
                        }


                        std::cout << pts << " ";
                        ofd.write((char *)pkt->data, pkt->size);

                        // 释放
                        av_packet_unref(pkt);
                    }

                    // 睡眠 40 毫秒
//                    std::this_thread::sleep_for(std::chrono::milliseconds(40));
                }
            }
        }
    }


    // 冲刷编码器
    re = avcodec_send_frame(encodec_ctx, NULL);
    if (re < 0) {
        std::cout <<  "avcodec_send_frame failed!" << std::endl;
        PrintErr(re);
        return -3;
    }
    if (re >= 0 ) {
        while (1) {
            re = avcodec_receive_packet(encodec_ctx, pkt);
            if (re == AVERROR(EAGAIN)) {
                av_packet_unref(pkt);
                continue;
            } else if (re == AVERROR_EOF) {
                av_packet_unref(pkt);
                break;
            } else if (re < 0) {
                av_packet_unref(pkt);
                break;
            }

            std::cout << pts << "- ";
            ofd.write((char *)pkt->data, pkt->size);
            av_packet_unref(pkt);
        }
    }

FAILED:
    // 内存回收
    ifs.close();
    av_frame_free(&frame);
    av_packet_free(&pkt);
    avcodec_free_context(&decodec_ctx);
    av_parser_close(parser_ctx);

    ofd.close();
    av_frame_free(&frame_yuv444);
    avcodec_free_context(&encodec_ctx);
    sws_free_context(&sws_ctx);

    return 0;
}

6.2 运行截图

相关推荐
程序员老陆3 小时前
FFmpeg 新编码 API 完全指南:avcodec_send_frame / avcodec_receive_packet
ffmpeg·音视频·编码
cpp_learners17 小时前
FFmpeg H264视频编码全流程解析
ffmpeg·音视频
cpp_learners1 天前
FFmpeg H264视频解码全流程解析
ffmpeg·音视频·解码
程序员老陆1 天前
FFmpeg 像素格式(pix_fmt)通关指南:从 YUV420P 到 NV12、RGB24 到底怎么选
ffmpeg·音视频·yuv·像素格式
DogDaoDao1 天前
FFmpeg 9.0 “Lei“ 正式发布:十年之后,以你之名
ffmpeg·音视频·实时音视频·视频编解码·ffprobe·ffmpeg 9.0
程序员老陆2 天前
FFmpeg 音频采样格式(sample_fmt)完全手册:FLTP、S16、S32 到底在吵什么
ffmpeg·音视频·音频采样格式
cpp_learner2 天前
FFmpeg H264视频编码全流程解析
ffmpeg
2 天前
使用ffmpeg将mp4视频转为m3u8格式
android·ffmpeg·音视频
xcLeigh3 天前
KingbaseES 的卢智能运维体架构深度拆解
运维·数据库·人工智能·ai·架构·ffmpeg·智能体