云计算百科
云计算领域专业知识百科平台

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

目录

  • 1. sws_getContext
  • 2. sws_scale
  • 3. 编码流程
    • 3.1 打开视频文件
    • 3.2 初始化解码器
    • 3.3 初始化音视频码流解析器
    • 3.4 初始化SDL部分
    • 3.5 初始化图像转换上下文
    • 3.6 读取文件数据
    • 3.7 解析音视频裸流数据
    • 3.8 解码
    • 3.9 像素格式转换
    • 3.10 SDL渲染
    • 3.11 冲刷解码器
    • 3.12 内存回收
  • 4. 代码合集
  • 5. 视频效果截图
  • 6. 进阶
    • 6.1 实现源码
    • 6.2 运行截图

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

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

调用流程:

sws_getContext() → 初始化转换上下文(只调用一次)

sws_scale() → 执行实际转换(每帧调用)

sws_freeContext() → 释放上下文(结束时调用)

运用流程:

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


1. sws_getContext

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

①函数原型

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

④使用示例

// 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 创建的上下文,将源图像数据转换为目标图像数据。

①函数原型

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,需要由使用者分配内存;

// 定义并且分配内存,用于接收转换后的一帧图像
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);// 分配内存

// 像素格式转换
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 打开视频文件

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 初始化解码器

// 查找解码器
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 初始化音视频码流解析器

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部分

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 初始化图像转换上下文

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 读取文件数据

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 解析音视频裸流数据

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 解码

// 发送解码
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 像素格式转换

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渲染

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 冲刷解码器

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 内存回收

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视频文件,解码后,由800×600,YUV420P转换为640×480,RGB24,再通过SDL2渲染;

#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. 进阶

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

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

6.1 实现源码

#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.将宽高修改为640×480,像素格式从 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 运行截图

在这里插入图片描述

赞(0)
未经允许不得转载:网硕互联帮助中心 » FFmpeg 像素格式转换与图像缩放实战
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!