#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/imgutils.h>
#include <libswscale/swscale.h>
// 输入输出路径
#define INPUT_FILE "input/video_7c93028305b4e734.mp4"
#define OUTPUT_FILE "images/first_frame.jpg"
/*
- 完整的数据流拓扑(无 Scheduler 版本):
-
- avformat_open_input() - 打开文件
-
- avformat_find_stream_info() - 读取流信息
-
- 找到视频流索引
-
- avcodec_find_decoder() - 查找解码器
-
- avcodec_alloc_context3() - 分配解码器上下文
-
- avcodec_parameters_to_context() - 复制参数
-
- avcodec_open2() - 打开解码器
-
- av_read_frame() - 读取 AVPacket(压缩数据)
-
- avcodec_send_packet() - 发送包到解码器
-
- avcodec_receive_frame() - 接收 AVFrame(原始数据)
-
- sws_getContext() - 创建图像转换上下文(YUV→RGB)
-
- sws_scale() - 转换像素格式
-
- 查找 MJPEG 编码器
-
- avcodec_alloc_context3() - 分配编码器上下文
-
- 设置编码参数
-
- avcodec_open2() - 打开编码器
-
- avcodec_send_frame() - 发送帧到编码器
-
- avcodec_receive_packet() - 接收编码后的包
-
- 写入文件
-
- 清理资源
*/
- 清理资源
int main(int argc, char *argv[])
{
int ret = 0;
// ============================================================
// 第一部分:解复用(Demuxing)
// ============================================================
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("阶段 1: 解复用(Demuxing)\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
// 1. 打开输入文件
AVFormatContext *fmt_ctx = NULL;
printf("1. avformat_open_input() - 打开输入文件\n");
printf(" 输入: %s\n", INPUT_FILE);
ret = avformat_open_input(&fmt_ctx, INPUT_FILE, NULL, NULL);
if (ret < 0) {
char errbuf[128];
av_strerror(ret, errbuf, sizeof(errbuf));
fprintf(stderr, "❌ 无法打开文件: %s\n", errbuf);
return -1;
}
printf(" ✅ 文件已打开\n\n");
// 2. 读取流信息
printf("2. avformat_find_stream_info() - 读取流信息\n");
ret = avformat_find_stream_info(fmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "❌ 无法读取流信息\n");
avformat_close_input(&fmt_ctx);
return -1;
}
printf(" ✅ 找到 %d 个流\n", fmt_ctx->nb_streams);
printf(" - 容器格式: %s\n", fmt_ctx->iformat->name);
printf(" - 时长: %lld 秒\n\n", fmt_ctx->duration / AV_TIME_BASE);
// 3. 查找视频流
printf("3. 查找视频流\n");
int video_stream_idx = -1;
AVCodecParameters *codec_params = NULL;
for (int i = 0; i < fmt_ctx->nb_streams; i++) {
AVCodecParameters *params = fmt_ctx->streams[i]->codecpar;
printf(" 流 #%d: ", i);
switch (params->codec_type) {
case AVMEDIA_TYPE_VIDEO:
printf("视频 - %s, %dx%d, %d fps\n",
avcodec_get_name(params->codec_id),
params->width, params->height,
fmt_ctx->streams[i]->avg_frame_rate.num /
fmt_ctx->streams[i]->avg_frame_rate.den);
if (video_stream_idx == -1) {
video_stream_idx = i;
codec_params = params;
}
break;
case AVMEDIA_TYPE_AUDIO:
printf("音频 - %s, %d Hz, %d 声道\n",
avcodec_get_name(params->codec_id),
params->sample_rate,
params->ch_layout.nb_channels);
break;
case AVMEDIA_TYPE_SUBTITLE:
printf("字幕 - %s\n", avcodec_get_name(params->codec_id));
break;
default:
printf("其他\n");
break;
}
}
if (video_stream_idx == -1) {
fprintf(stderr, "❌ 未找到视频流\n");
avformat_close_input(&fmt_ctx);
return -1;
}
printf(" ✅ 使用视频流 #%d\n\n", video_stream_idx);
// ============================================================
// 第二部分:解码(Decoding)
// ============================================================
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("阶段 2: 解码(Decoding)\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
// 4. 查找解码器
printf("4. avcodec_find_decoder() - 查找解码器\n");
const AVCodec *decoder = avcodec_find_decoder(codec_params->codec_id);
if (!decoder) {
fprintf(stderr, "❌ 未找到解码器 (codec_id=%d)\n", codec_params->codec_id);
avformat_close_input(&fmt_ctx);
return -1;
}
printf(" ✅ 找到解码器: %s\n\n", decoder->name);
// 5. 分配解码器上下文
printf("5. avcodec_alloc_context3() - 分配解码器上下文\n");
AVCodecContext *dec_ctx = avcodec_alloc_context3(decoder);
if (!dec_ctx) {
fprintf(stderr, "❌ 无法分配解码器上下文\n");
avformat_close_input(&fmt_ctx);
return -1;
}
printf(" ✅ 解码器上下文已分配\n\n");
// 6. 复制编解码器参数
printf("6. avcodec_parameters_to_context() - 复制编解码参数\n");
ret = avcodec_parameters_to_context(dec_ctx, codec_params);
if (ret < 0) {
fprintf(stderr, "❌ 无法复制参数\n");
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
return -1;
}
printf(" ✅ 参数已复制\n");
printf(" - 分辨率: %dx%d\n", dec_ctx->width, dec_ctx->height);
printf(" - 像素格式: %s\n\n", av_get_pix_fmt_name(dec_ctx->pix_fmt));
// 7. 打开解码器
printf("7. avcodec_open2() - 打开解码器\n");
ret = avcodec_open2(dec_ctx, decoder, NULL);
if (ret < 0) {
fprintf(stderr, "❌ 无法打开解码器\n");
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
return -1;
}
printf(" ✅ 解码器已打开\n\n");
// 8. 分配 AVPacket 和 AVFrame
printf("8. 分配数据结构\n");
AVPacket *packet = av_packet_alloc();
AVFrame *frame = av_frame_alloc();
if (!packet || !frame) {
fprintf(stderr, "❌ 无法分配内存\n");
goto cleanup;
}
printf(" ✅ AVPacket 和 AVFrame 已分配\n\n");
// 9. 读取并解码第一个视频帧
printf("9. 读取并解码第一帧\n");
int frame_decoded = 0;
while (av_read_frame(fmt_ctx, packet) >= 0) {
// 只处理视频流的包
if (packet->stream_index != video_stream_idx) {
av_packet_unref(packet);
continue;
}
printf(" → av_read_frame() 读取到 AVPacket\n");
printf(" - 大小: %d 字节\n", packet->size);
printf(" - PTS: %lld\n", packet->pts);
printf(" - DTS: %lld\n", packet->dts);
printf(" - 关键帧: %s\n\n", (packet->flags & AV_PKT_FLAG_KEY) ? "是" : "否");
// 10. 发送包到解码器
printf(" → avcodec_send_packet() 发送到解码器\n");
ret = avcodec_send_packet(dec_ctx, packet);
if (ret < 0) {
fprintf(stderr, "❌ 发送包失败\n");
av_packet_unref(packet);
break;
}
printf(" ✅ 包已发送\n\n");
// 11. 从解码器接收帧
printf(" → avcodec_receive_frame() 接收解码帧\n");
ret = avcodec_receive_frame(dec_ctx, frame);
if (ret == AVERROR(EAGAIN)) {
printf(" ⏳ 需要更多数据\n\n");
av_packet_unref(packet);
continue;
} else if (ret < 0) {
fprintf(stderr, "❌ 接收帧失败\n");
av_packet_unref(packet);
break;
}
printf(" ✅ 成功解码 AVFrame\n");
printf(" - 格式: %s\n", av_get_pix_fmt_name(frame->format));
printf(" - 分辨率: %dx%d\n", frame->width, frame->height);
printf(" - PTS: %lld\n", frame->pts);
// printf(" - 关键帧: %s\n\n", frame->key_frame ? "是" : "否");
frame_decoded = 1;
av_packet_unref(packet);
break; // 只需要第一帧
}
if (!frame_decoded) {
fprintf(stderr, "❌ 未能解码任何帧\n");
goto cleanup;
}
// ============================================================
// 第三部分:像素格式转换(如果需要)
// ============================================================
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("阶段 3: 像素格式转换\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
// JPEG 编码器通常使用 YUVJ420P 或 RGB24
enum AVPixelFormat dst_pix_fmt = AV_PIX_FMT_YUVJ420P;
printf("10. sws_getContext() - 创建图像转换上下文\n");
printf(" 源格式: %s → 目标格式: %s\n",
av_get_pix_fmt_name(frame->format),
av_get_pix_fmt_name(dst_pix_fmt));
struct SwsContext *sws_ctx = sws_getContext(
frame->width, frame->height, frame->format, // 源
frame->width, frame->height, dst_pix_fmt, // 目标
SWS_BILINEAR, NULL, NULL, NULL // 算法
);
if (!sws_ctx) {
fprintf(stderr, "❌ 无法创建转换上下文\n");
goto cleanup;
}
printf(" ✅ 转换上下文已创建\n\n");
// 分配目标帧
AVFrame *frame_rgb = av_frame_alloc();
frame_rgb->format = dst_pix_fmt;
frame_rgb->width = frame->width;
frame_rgb->height = frame->height;
ret = av_frame_get_buffer(frame_rgb, 0);
if (ret < 0) {
fprintf(stderr, "❌ 无法分配帧缓冲\n");
sws_freeContext(sws_ctx);
goto cleanup;
}
// 11. 转换像素格式
printf("11. sws_scale() - 转换像素格式\n");
sws_scale(sws_ctx,
(const uint8_t *const *)frame->data, frame->linesize, 0, frame->height,
frame_rgb->data, frame_rgb->linesize);
printf(" ✅ 像素格式已转换\n\n");
sws_freeContext(sws_ctx);
// ============================================================
// 第四部分:编码为 JPEG(Encoding)
// ============================================================
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("阶段 4: 编码为 JPEG\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
// 12. 查找 MJPEG 编码器
printf("12. avcodec_find_encoder() - 查找 MJPEG 编码器\n");
const AVCodec *encoder = avcodec_find_encoder(AV_CODEC_ID_MJPEG);
if (!encoder) {
fprintf(stderr, "❌ 未找到 MJPEG 编码器\n");
goto cleanup;
}
printf(" ✅ 找到编码器: %s\n\n", encoder->name);
// 13. 分配编码器上下文
printf("13. avcodec_alloc_context3() - 分配编码器上下文\n");
AVCodecContext *enc_ctx = avcodec_alloc_context3(encoder);
if (!enc_ctx) {
fprintf(stderr, "❌ 无法分配编码器上下文\n");
goto cleanup;
}
printf(" ✅ 编码器上下文已分配\n\n");
// 14. 设置编码参数
printf("14. 设置编码参数\n");
enc_ctx->width = frame_rgb->width;
enc_ctx->height = frame_rgb->height;
enc_ctx->pix_fmt = dst_pix_fmt;
enc_ctx->time_base = (AVRational){1, 25}; // 帧率(对单帧无影响)
enc_ctx->codec_type = AVMEDIA_TYPE_VIDEO;
enc_ctx->qmin = 2; // 最小量化参数(质量)
enc_ctx->qmax = 31; // 最大量化参数
enc_ctx->global_quality = 5 * FF_QP2LAMBDA; // 质量参数(越小越好)
printf(" - 分辨率: %dx%d\n", enc_ctx->width, enc_ctx->height);
printf(" - 像素格式: %s\n", av_get_pix_fmt_name(enc_ctx->pix_fmt));
printf(" - 质量: %d\n\n", enc_ctx->global_quality);
// 15. 打开编码器
printf("15. avcodec_open2() - 打开编码器\n");
ret = avcodec_open2(enc_ctx, encoder, NULL);
if (ret < 0) {
fprintf(stderr, "❌ 无法打开编码器\n");
avcodec_free_context(&enc_ctx);
goto cleanup;
}
printf(" ✅ 编码器已打开\n\n");
// 16. 发送帧到编码器
printf("16. avcodec_send_frame() - 发送帧到编码器\n");
frame_rgb->pict_type = AV_PICTURE_TYPE_I; // 强制为关键帧
ret = avcodec_send_frame(enc_ctx, frame_rgb);
if (ret < 0) {
fprintf(stderr, "❌ 发送帧失败\n");
avcodec_free_context(&enc_ctx);
goto cleanup;
}
printf(" ✅ 帧已发送\n\n");
// 17. 从编码器接收包
printf("17. avcodec_receive_packet() - 接收编码包\n");
AVPacket *enc_packet = av_packet_alloc();
ret = avcodec_receive_packet(enc_ctx, enc_packet);
if (ret < 0) {
fprintf(stderr, "❌ 接收包失败\n");
av_packet_free(&enc_packet);
avcodec_free_context(&enc_ctx);
goto cleanup;
}
printf(" ✅ 成功编码为 JPEG\n");
printf(" - 大小: %d 字节\n\n", enc_packet->size);
// ============================================================
// 第五部分:写入文件(Muxing)
// ============================================================
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("阶段 5: 写入文件\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
printf("18. 写入 JPEG 文件\n");
printf(" 输出: %s\n", OUTPUT_FILE);
FILE *output = fopen(OUTPUT_FILE, "wb");
if (!output) {
fprintf(stderr, "❌ 无法创建输出文件\n");
av_packet_free(&enc_packet);
avcodec_free_context(&enc_ctx);
goto cleanup;
}
fwrite(enc_packet->data, 1, enc_packet->size, output);
fclose(output);
printf(" ✅ 文件已写入 (%d 字节)\n\n", enc_packet->size);
// 清理编码器资源
av_packet_free(&enc_packet);
avcodec_free_context(&enc_ctx);
// ============================================================
// 第六部分:清理资源
// ============================================================
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("阶段 6: 清理资源\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n");
cleanup:
printf("19. 释放所有资源\n");
if (frame_rgb)
av_frame_free(&frame_rgb);
if (frame)
av_frame_free(&frame);
if (packet)
av_packet_free(&packet);
if (dec_ctx)
avcodec_free_context(&dec_ctx);
if (fmt_ctx)
avformat_close_input(&fmt_ctx);
printf(" ✅ 所有资源已释放\n\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
printf("✅ 完成!第一帧已提取并保存\n");
printf("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n");
return 0;
}