FFmpeg8.0.1 编解码流程

#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 版本):
    1. avformat_open_input() - 打开文件
    1. avformat_find_stream_info() - 读取流信息
    1. 找到视频流索引
    1. avcodec_find_decoder() - 查找解码器
    1. avcodec_alloc_context3() - 分配解码器上下文
    1. avcodec_parameters_to_context() - 复制参数
    1. avcodec_open2() - 打开解码器
    1. av_read_frame() - 读取 AVPacket(压缩数据)
    1. avcodec_send_packet() - 发送包到解码器
    1. avcodec_receive_frame() - 接收 AVFrame(原始数据)
    1. sws_getContext() - 创建图像转换上下文(YUV→RGB)
    1. sws_scale() - 转换像素格式
    1. 查找 MJPEG 编码器
    1. avcodec_alloc_context3() - 分配编码器上下文
    1. 设置编码参数
    1. avcodec_open2() - 打开编码器
    1. avcodec_send_frame() - 发送帧到编码器
    1. avcodec_receive_packet() - 接收编码后的包
    1. 写入文件
    1. 清理资源
      */

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;

}

相关推荐
qs70169 小时前
c直接调用FFmpeg命令无法执行问题
c语言·开发语言·ffmpeg
止礼9 小时前
FFmpeg8.0.1 Mac环境 CMake本地调试配置
macos·ffmpeg
简鹿视频1 天前
视频转mp4格式具体作步骤
ffmpeg·php·音视频·实时音视频
一点晖光1 天前
centos安装ffmpeg环境
linux·ffmpeg·centos
炒毛豆1 天前
前端直播开发入门:搞懂推流拉流,掌握播放器核心
ffmpeg
八月的雨季 最後的冰吻2 天前
FFmepg-- 32-ffplay源码- PacketQueue 的线程安全机制 以及 serial 字段的作用
安全·ffmpeg
凯新生物2 天前
mPEG-SS-PLGA-DTX:智能药物递送系统
eureka·flink·ffmpeg·etcd
学而知不足~2 天前
字幕转码杂记
ffmpeg