当前位置: 首页 > news >正文

FFmpeg05:编解码实战

基础介绍

结构体和函数

AVCodec 编码器结构体
AVCodecContext 编码器上下文
AVFrame 解码后的帧

av_frame_alloc/av_frame_free()
avcodec_alloc_context3()
avcodec_free_context()

解码步骤

  • 查找解码器(avcodec_find_decoder)
  • 打开解码器(avcodec_open2)
  • 解码(avcodec_decode_video2)

视频编码

指定输出文件为out.h264,编码器为libx264
伪造YUV420P数据进行测试

extern "C" {#include <libavutil/log.h>#include <libavcodec/avcodec.h>#include <libavutil/opt.h>
}
#include <string>
using namespace std;static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out) {int ret = -1;ret = avcodec_send_frame(ctx, frame);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "send frame to encoder error\n");goto _END;}while (ret >= 0) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {return -1;}fwrite(pkt->data, pkt->size, 1, out);av_packet_unref(pkt);}_END:return 0;
}int main(int argc, char* argv[]) {av_log_set_level(AV_LOG_DEBUG);string dst;string codec_name;int ret = -1;const AVCodec *codec = nullptr;AVCodecContext *codec_ctx = nullptr;FILE *fp = nullptr;AVFrame *frame = nullptr;AVPacket *pkt = nullptr;// 1. 输入参数if (argc < 3) {av_log(nullptr, AV_LOG_ERROR, "arguments must be more than 2\n");goto _ERROR;}dst = argv[1];codec_name = argv[2];// 2. 查找编码器codec = avcodec_find_encoder_by_name(codec_name.c_str());if (!codec) {av_log(nullptr, AV_LOG_ERROR, "could not find encoder %s\n", codec_name.c_str());goto _ERROR;}// 3. 创建编码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {av_log(nullptr, AV_LOG_ERROR, "could not allocate codec context\n");goto _ERROR;}// 4. 设置编码器参数codec_ctx->width = 640;codec_ctx->height = 480;codec_ctx->bit_rate = 500000;codec_ctx->time_base = (AVRational){1, 25};codec_ctx->framerate = (AVRational){25, 1};codec_ctx->gop_size = 10; // 每10帧一个关键帧codec_ctx->max_b_frames = 1; // 一个gop允许1个Bcodec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;// 设置264编码器的特殊参数if (codec->id == AV_CODEC_ID_H264) {// priv_data是编码器私有数据// 通过它可以设置编码器私有参数// 这些参数在不同编码器中是不一样的av_opt_set(codec_ctx->priv_data, "preset", "slow", 0);}// 5. 编码器与编码器上下文绑定到一起ret = avcodec_open2(codec_ctx, codec, nullptr);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "could not open codec %s\n", codec_name.c_str());goto _ERROR;}// 6. 创建输出文件fp = fopen(dst.c_str(), "wb+");if (!fp) {av_log(nullptr, AV_LOG_ERROR, "could not open file %s\n", dst.c_str());goto _ERROR;}// 7. 创建AVFrameframe = av_frame_alloc();if (!frame) {av_log(nullptr, AV_LOG_ERROR, "could not allocate frame\n");goto _ERROR;}frame->width = codec_ctx->width;frame->height = codec_ctx->height;frame->format = codec_ctx->pix_fmt;// 真正的像素数据分配,并与AVFrame绑定,后面设置为0,会自动根据cpu对齐ret = av_frame_get_buffer(frame, 0);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "could not allocate frame data\n");goto _ERROR;}// 8. 创建AVPacketpkt = av_packet_alloc();if (!pkt) {av_log(nullptr, AV_LOG_ERROR, "could not allocate packet\n");goto _ERROR;}// 9. 生成视频内容for (int i = 0; i < 25; ++i) {// 确保frame是否可写ret = av_frame_make_writable(frame);if (ret < 0) {break;}// Y分量for (int y = 0; y < codec_ctx->height; y++) {for (int x = 0; x < codec_ctx->width; x++) {frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;}}// UV分量for (int y = 0; y < codec_ctx->height / 2; y++) {for (int x = 0; x < codec_ctx->width / 2; x++) {// U分量:128是黑色frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;// V分量:64是黑色frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;}}frame->pts = i;// 10. 编码ret = encode(codec_ctx, frame, pkt, fp);}// 10. 编码 输入空帧目的是输出队列中剩余的数据encode(codec_ctx, nullptr, pkt, fp);_ERROR:if (fp) {fclose(fp);fp = nullptr;}if (codec_ctx) {avcodec_free_context(&codec_ctx);codec_ctx = nullptr;}if (frame) {av_frame_free(&frame);frame = nullptr;}if (pkt) { av_packet_free(&pkt);pkt = nullptr;}return 0;
}

音频编码

与视频编码不同的内容:

  • 编码器参数不一样
  • 创建的帧的参数不一样
  • 构建音频的方式与视频的方式差距很大 但是先不用管
extern "C" {#include <libavutil/log.h>#include <libavcodec/avcodec.h>#include <libavutil/opt.h>
}
#include <string>
using namespace std;static int select_best_samplerate(const AVCodec *codec) {const int *p = codec->supported_samplerates;int best_samplerate = 0;if (!p) {return 44100;}while (*p) {// 找到最接近44100的采样率if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate)) {best_samplerate = *p;}p++;}return best_samplerate;
}static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt) {const enum AVSampleFormat *p = codec->sample_fmts;while (*p != AV_SAMPLE_FMT_NONE) {if (*p == sample_fmt) {return 1;}p++;}av_log(nullptr, AV_LOG_ERROR, "sample format %s not support\n", av_get_sample_fmt_name(sample_fmt));return 0;
}static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *out) {int ret = -1;ret = avcodec_send_frame(ctx, frame);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "send frame to encoder error\n");goto _END;}while (ret >= 0) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {return -1;}fwrite(pkt->data, pkt->size, 1, out);av_packet_unref(pkt);}_END:return 0;
}
/*** 参数1:输出文件名* 参数2:编码器名称,比如 libfdk-aac 暂时去掉*/
int main(int argc, char* argv[]) {av_log_set_level(AV_LOG_DEBUG);string dst;// string codec_name;int ret = -1;const AVCodec *codec = nullptr;AVCodecContext *codec_ctx = nullptr;FILE *fp = nullptr;AVFrame *frame = nullptr;AVPacket *pkt = nullptr;uint16_t *samples = nullptr;float t = 0;float tincr = 0;AVChannelLayout stereo_layout;// 1. 输入参数if (argc < 3) {av_log(nullptr, AV_LOG_ERROR, "arguments must be more than 2\n");goto _ERROR;}dst = argv[1];// codec_name = argv[2];// 2. 查找编码器// codec = avcodec_find_encoder_by_name(codec_name.c_str());codec = avcodec_find_encoder_by_name("libfdk_aac");  // MAC默认不带这个,需要自己编译, 支持// codec = avcodec_find_encoder(AV_CODEC_ID_AAC);if (!codec) {av_log(nullptr, AV_LOG_ERROR, "could not find encoder aac\n");goto _ERROR;}// 3. 创建编码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {av_log(nullptr, AV_LOG_ERROR, "could not allocate codec context\n");goto _ERROR;}// 4. 设置编码器参数codec_ctx->bit_rate = 64000;codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) {av_log(nullptr, AV_LOG_ERROR, "encoder does not support sample format %s\n", av_get_sample_fmt_name(codec_ctx->sample_fmt));goto _ERROR;}codec_ctx->sample_rate = select_best_samplerate(codec);av_channel_layout_copy(&codec_ctx->ch_layout, &stereo_layout);// 设置264编码器的特殊参数if (codec->id == AV_CODEC_ID_H264) {// priv_data是编码器私有数据// 通过它可以设置编码器私有参数// 这些参数在不同编码器中是不一样的av_opt_set(codec_ctx->priv_data, "preset", "slow", 0);}// 5. 编码器与编码器上下文绑定到一起ret = avcodec_open2(codec_ctx, codec, nullptr);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "could not open codec aac\n");goto _ERROR;}// 6. 创建输出文件fp = fopen(dst.c_str(), "wb+");if (!fp) {av_log(nullptr, AV_LOG_ERROR, "could not open file %s\n", dst.c_str());goto _ERROR;}// 7. 创建AVFrameframe = av_frame_alloc();if (!frame) {av_log(nullptr, AV_LOG_ERROR, "could not allocate frame\n");goto _ERROR;}frame->nb_samples = codec_ctx->frame_size; // 每个通道的采样点数frame->format = codec_ctx->sample_fmt;av_channel_layout_copy(&frame->ch_layout, &codec_ctx->ch_layout);// 真正的数据分配,并与AVFrame绑定,后面设置为0,会自动根据cpu对齐ret = av_frame_get_buffer(frame, 0);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "could not allocate frame data\n");goto _ERROR;}// 8. 创建AVPacketpkt = av_packet_alloc();if (!pkt) {av_log(nullptr, AV_LOG_ERROR, "could not allocate packet\n");goto _ERROR;}tincr = 2 * M_PI * 440.0 / codec_ctx->sample_rate; // 440hz// 9. 生成音频内容for (int i = 0; i < 200; i++) { // 200 * 1024 / 44100 = 4.6s// 设置数据ret = av_frame_make_writable(frame);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "frame not writable\n");goto _ERROR;}samples = (uint16_t*)frame->data[0];for (int j = 0; j < codec_ctx->frame_size; j++) {samples[2*j] = (int)sin(t) * 10000;for (int k = 1; k < codec_ctx->ch_layout.nb_channels; k++) {samples[2*j + k] = samples[2*j];}t += tincr;}// 编码ret = encode(codec_ctx, frame, pkt, fp);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "encode error\n");goto _ERROR;}}// 10. 编码 输入空帧目的是输出队列中剩余的数据encode(codec_ctx, nullptr, pkt, fp);_ERROR:if (fp) {fclose(fp);fp = nullptr;}if (codec_ctx) {avcodec_free_context(&codec_ctx);codec_ctx = nullptr;}if (frame) {av_frame_free(&frame);frame = nullptr;}if (pkt) { av_packet_free(&pkt);pkt = nullptr;}return 0;
}

生成图片

解码视频并保存为PGM或BMP图
其中PGM是只有灰度;
而BMP是彩色的,但确实四位对齐的,所以需要做一些额外操作:

  • 需要内存对齐#pragma pack(push, 1):保证写入的结构体的大小和规范中一致,而不被cpp内存影响
  • 又由于BMP是四字节对齐,不能直接把帧数据全部写入,而是一行一行写入,遇到一行数据不是4的倍数的,需要补0来padding
#include <iostream>
using namespace std;
extern "C" {#include <libavformat/avformat.h>#include <libavutil/log.h>#include <libavutil/avutil.h>#include <libavcodec/avcodec.h>#include <libavutil/opt.h>#include <libswscale/swscale.h>
}
#define WORD uint16_t
#define DWORD uint32_t
#define LONG int32_t#pragma pack(push, 1) // 强制1字节对齐
typedef struct tagBITMAPFILEHEADER {WORD  bfType;DWORD bfSize;WORD  bfReserved1;WORD  bfReserved2;DWORD bfOffBits;
} BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;typedef struct tagBITMAPINFOHEADER {DWORD biSize;LONG  biWidth;LONG  biHeight;WORD  biPlanes;WORD  biBitCount;DWORD biCompression;DWORD biSizeImage;LONG  biXPelsPerMeter;LONG  biYPelsPerMeter;DWORD biClrUsed;DWORD biClrImportant;
} BITMAPINFOHEADER, *LPBITMAPINFOHEADER, *PBITMAPINFOHEADER;static int savePic(unsigned char *buf, int linesize, int width, int height, string fileName) {FILE *fp = fopen(fileName.c_str(), "wb+");if (!fp) {av_log(nullptr, AV_LOG_ERROR, "could not open %s\n", fileName.c_str());return -1;}// 写入头部信息,固定值,这个是PGM格式的magic头fprintf(fp, "P5\n%d %d\n255\n", width, height);for (int i = 0; i < height; i++) {fwrite(buf + i * linesize, 1, width, fp);}fclose(fp);return 0;
}
static void saveBMP(SwsContext *sws_ctx, AVFrame *frame, int w, int h, string fileName) {int dataSize = w * h * 3;FILE *fp = nullptr;// 1. 先进行转换,将YUV frame转成BGR24 frameAVFrame *frameBGR = av_frame_alloc();frameBGR->width = w;frameBGR->height = h;frameBGR->format = AV_PIX_FMT_BGR24;av_frame_get_buffer(frameBGR, 0);sws_scale(sws_ctx, frame->data, frame->linesize, 0, frame->height, frameBGR->data, frameBGR->linesize);// BMP规范:每行字节数必须 4 字节对齐int rowSize = (w * 3 + 3) & ~3;int imageSize = rowSize * h;// 2. 构造BITMAPINFOHEADERBITMAPINFOHEADER infoHeader;infoHeader.biSize = sizeof(BITMAPINFOHEADER);infoHeader.biWidth = w;infoHeader.biHeight = h * (-1);infoHeader.biBitCount = 24;infoHeader.biCompression = 0;infoHeader.biClrImportant = 0;infoHeader.biClrUsed = 0;infoHeader.biXPelsPerMeter = 0;infoHeader.biYPelsPerMeter = 0;infoHeader.biPlanes = 1;// 3. 构造BITMAPFILEHEADERBITMAPFILEHEADER fileHeader = {0,};fileHeader.bfType = 0x4d42; // "BM"fileHeader.bfSize = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) + imageSize;fileHeader.bfOffBits = sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER);// 4. 将数据写入fp = fopen(fileName.c_str(), "wb");fwrite(&fileHeader, sizeof(BITMAPFILEHEADER), 1, fp);fwrite(&infoHeader, sizeof(BITMAPINFOHEADER), 1, fp);// 每行写入,注意 frameBGR->linesize[0] 可能大于 w*3uint8_t *srcData = frameBGR->data[0];for (int y = 0; y < h; y++) {fwrite(srcData + y * frameBGR->linesize[0], 1, w * 3, fp);// 如果 w*3 不是 4 的倍数,要补 paddinguint8_t padding[3] = {0, 0, 0};fwrite(padding, 1, rowSize - w * 3, fp);}// fwrite(frameBGR->data[0], 1, dataSize, fp); // 不能直接这样写,因为BMP是每行都是4位对齐的,只能逐行写入,不够四位需要补零// 5. 释放资源fclose(fp);av_freep(&frameBGR->data[0]);av_free(frameBGR);
}static int decode(AVCodecContext *ctx, SwsContext *sws_ctx, AVFrame *frame, AVPacket *pkt, string fileName) {int ret = -1;ret = avcodec_send_packet(ctx, pkt);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "send frame to encoder error\n");goto _END;}while (ret >= 0) {ret = avcodec_receive_frame(ctx, frame);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {return -1;}string buf = "";buf += fileName;buf += "-";buf += to_string(ctx->frame_num);buf += ".bmp";// saveBMP(sws_ctx, frame, frame->width, frame->height, buf);savePic(frame->data[0], frame->linesize[0], frame->width, frame->height, buf);if (pkt) {av_packet_unref(pkt);}}_END:return 0;
}/** * argv[0]: 可执行程序的路径* argv[1]: 源文件路径* argv[2]: 目的文件路径* */ 
int main(int argc, char* argv[]) {// 1. 处理参数string src;string dst;int ret = -1;int idx = -1;AVFormatContext *pFmtCtx = nullptr;AVStream *inStream = nullptr;AVPacket *pkt = nullptr;AVFrame *frame = nullptr;const AVCodec *codec = nullptr;AVCodecContext *codec_ctx = nullptr;SwsContext *sws_ctx = nullptr;av_log_set_level(AV_LOG_INFO);if (argc < 3) {av_log(nullptr, AV_LOG_ERROR, "arguments must be more than 2\n");exit(-1);}src = argv[1];dst = argv[2];// 2. 打开多媒体文件ret = avformat_open_input(&pFmtCtx, src.c_str(), nullptr, nullptr);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "%s\n", av_err2str(ret));exit(-1);}// 3. 从多媒体文件中找到视频流idx = av_find_best_stream(pFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);if (idx < 0) {av_log(pFmtCtx, AV_LOG_ERROR, "could not find audio stream in %s\n", src.c_str());goto _ERROR;}inStream = pFmtCtx->streams[idx];// 4. 查找解码器codec = avcodec_find_decoder(inStream->codecpar->codec_id);if (!codec) {av_log(nullptr, AV_LOG_ERROR, "could not find encoder x264\n");goto _ERROR;}// 5. 创建解码器上下文codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {av_log(nullptr, AV_LOG_ERROR, "could not allocate codec context\n");goto _ERROR;}// 6. 根据流参数,设置解码器上下文avcodec_parameters_to_context(codec_ctx, inStream->codecpar);// 7. 解码器与解码器上下文绑定到一起ret = avcodec_open2(codec_ctx, codec, nullptr);if (ret < 0) {av_log(nullptr, AV_LOG_ERROR, "could not open codec x264\n");goto _ERROR;}// 7.1 获得SWSContextsws_ctx = sws_getContext(codec_ctx->width, codec_ctx->height, AV_PIX_FMT_YUV420P, codec_ctx->width, codec_ctx->height, AV_PIX_FMT_BGR24, SWS_BICUBIC, nullptr, nullptr, nullptr);// 8. 创建AVFrameframe = av_frame_alloc();if (!frame) {av_log(nullptr, AV_LOG_ERROR, "could not allocate frame\n");goto _ERROR;}// 9. 创建AVPacketpkt = av_packet_alloc();if (!pkt) {av_log(nullptr, AV_LOG_ERROR, "could not allocate packet\n");goto _ERROR;}// 10. 读取视频帧,进行编码,写入目的文件while (av_read_frame(pFmtCtx, pkt) >= 0) {if (pkt->stream_index == idx) {decode(codec_ctx, sws_ctx, frame, pkt, dst);}av_packet_unref(pkt);}decode(codec_ctx, sws_ctx, frame, nullptr, dst); // 这里传nullptr,表示刷新编码器// 11. 释放资源,关闭文件
_ERROR:if (pFmtCtx) {avformat_close_input(&pFmtCtx);pFmtCtx = nullptr;}if (codec_ctx) {avcodec_free_context(&codec_ctx);codec_ctx = nullptr;}if (frame) {av_frame_free(&frame);frame = nullptr;}if (pkt) {av_packet_free(&pkt);pkt = nullptr;}return 0;
}#pragma pack(pop) // 恢复对齐设置
http://www.dtcms.com/a/354804.html

相关文章:

  • 机器学习框架下:金价近3400关口波动,AI量化模型对PCE数据的动态监测与趋势预测
  • 企业通讯软件以安全为基,搭建高效的通讯办公平台
  • RA4M2环境搭建与新建工程
  • 新手向:Python开发简易股票价格追踪器
  • Linux内核IPv4 RAW套接字深度解析:从数据包构造到可靠传输的挑战
  • Dify 和 LangChain 区别对比总结
  • 【实操教学】ArcGIS 如何进行定义坐标系
  • Python实现点云基于法向量、曲率和ISS提取特征点
  • 【GM3568JHF】FPGA+ARM异构开发板 使用指南:显示与触摸
  • 第二章:Cesium 视图控制与相机操作
  • Java集合操作:Apache Commons Collections4启示录
  • React中优雅管理CSS变量的最佳实践
  • iOS文件管理在uni-app开发中的实战应用,多工具解决
  • 三、计算机网络与分布式系统(上)
  • Subdev与Media子系统的数据结构
  • 线程池及线程池单例模式
  • 图数据库neo4j的安装
  • Go语言数组完全指南
  • 基于Springboot的酒店房间预订系统源码
  • More Effective C++ 条款13:以by reference方式捕捉exceptions
  • [Mysql数据库] 知识点总结5
  • 【C++游记】物种多样——谓之多态
  • 49个Docker自动化脚本:覆盖全场景运维,构建高可用容器体系
  • 【C初阶】文件操作
  • Claude Code 流畅使用指南
  • java中sleep与wait的区别
  • ES基础知识
  • PostgreSQL15——常用函数
  • docker一键部署!强大的本地音乐服务器NAS-Music
  • labelme的安装