惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

月光博客
月光博客
Stack Overflow Blog
Stack Overflow Blog
L
LangChain Blog
Jina AI
Jina AI
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
雷峰网
雷峰网
T
Tailwind CSS Blog
MongoDB | Blog
MongoDB | Blog
博客园 - 【当耐特】
博客园 - 聂微东
V
Visual Studio Blog
博客园_首页
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The Cloudflare Blog
人人都是产品经理
人人都是产品经理
Apple Machine Learning Research
Apple Machine Learning Research
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
GbyAI
GbyAI
F
Fortinet All Blogs
C
Check Point Blog
罗磊的独立博客
H
Hackread – Cybersecurity News, Data Breaches, AI and More

博客园 - 飘杨......

为ZLMediaKit在windows10编译srtp Windows10安装部署ZLMediaKit SurfaceView+EGL+OpenGL ES+ffmpeg播放视频并解决黑屏不显示画面的问题 SurfaceView+ANativeWindow+ffmpeg播放视频并解决花屏问题 SDL播放音频工具类封装 Qt QWidget中dropEvent(QDropEvent* e)拖拽放开不接收事件/不执行,且拖拽到目标后鼠标显示禁用符号 ffmpeg视频转码遇到H264黑屏问题 ffmpeg根据输入的参数截取视频 ffmpeg解封装mp4并解码渲染(SDL2) Qt+ffmpeg+SDL硬件解码并渲染 ffmpeg+sdl播放H264/H265文件 FFmpeg实战:录制Windows屏幕并用FFmpeg压缩成H.264/H.265并保存 ffmpeg SwsContext像素格式转换BGRA转YUV420P H264/H265 NALU的nalu_unit_type解析 ffmpeg码率控制模式详解 ffmpeg-H264/H265常用编解码预设参数 Qt+FFmpeg+SDL 实现多路多格式原始视频播放工具:从像素处理到实时渲染 Qt+SDL 实战:多格式视频播放工具开发 —— 支持 RGBA/ARGB/RGB24/YUV420P 的完整实现 SDL将YUV视频渲染到Qt窗口播放
ffmpeg将视频流录制下来并保存到本地
飘杨...... · 2025-05-28 · via 博客园 - 飘杨......

一、概述

  保存网络流为mp4存储到本地是一个通用的并且常用的需求,例如在视频监控、网络直播领域会根据不同级别以及重要程度保存媒体流到本地一定的时间,有些要求一周,有些要求半年、一年乃至几年的都有。

如何保存这些流就成为了关键。当然有些需求还涉及到了转码,但是本节只讲如何将流原封不动的保存下来,不考虑转码。转码的需求将在后续的文章中给出具体的方案。

  本节会模拟一个视频流,即输入一个mp4文件,通过解封装拿到AVPacket,并将AVPacket保存到本地的其他目录,从而达到模拟的效果。

  主要步骤:

    • 创建解封装上下文,并打开输入流。avformat_open_input
    • 发现流信息avformat_find_stream_info
    • 拿到输入流的音频流和视频流AVStream
    • 创建输出的封装器上下文AVFormatContext *oc;avformat_alloc_output_context2
    • 创建输出的音频流及视频流并和oc绑定。avformat_new_stream
    • 将输入流的参数copy到输出流中avcodec_parameters_copy
    • 打开输出流 avio_open
    • 写入文件头部avformat_write_header
    • 循环while(true)解封装av_read_frame.并将解封装后的AVPacket通过av_interleaved_write_frame写入到本地文件
    • 写入尾部av_write_trailer
    • 释放对象开辟的内存

二、代码示例

  • 创建解封装上下文,并打开输入流。avformat_open_input
    //1.创建解封装上下文,并打开文件
    AVFormatContext* ic = nullptr;
    int re = avformat_open_input(&ic, this->srcFilePath.toStdString().c_str(), NULL, NULL);
    if (re != 0)
    {
        PrintError(re, "avformat_open_input");
        return;
    }
  • 发现流信息avformat_find_stream_info
    //2.发现媒体信息流
    re = avformat_find_stream_info(ic, NULL);
    if (re < 0)
    {
        PrintError(re, "avformat_find_stream_info");
        return;
    }
  • 拿到输入流的音频流和视频流AVStream
    //3.分别找到音频流以及视频流
    AVStream* vs = nullptr;
    AVStream* as = nullptr;
    for (int i = 0;i < ic->nb_streams;i++)
    {
        if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            as = ic->streams[i];
        }
        else if (ic->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            vs = ic->streams[i];
        }
    }
  • 创建输出的封装器上下文AVFormatContext *oc;avformat_alloc_output_context2
    ///4.创建封装器上下文
    AVFormatContext* oc = nullptr;
    re = avformat_alloc_output_context2(&oc, NULL, NULL, this->dstFilePath.toStdString().c_str());
    if (re < 0)
    {
        PrintError(re, "avformat_alloc_output_context2");
        return;
    }
  • 创建输出的音频流及视频流并和oc绑定。avformat_new_stream
    //添加音频流以及视频流
    auto ovs = avformat_new_stream(oc, NULL);
    auto oas = avformat_new_stream(oc, NULL);
  • 将输入流的参数copy到输出流中avcodec_parameters_copy
    //将输入流的编解码参数,复制到输出流中
    if (vs)
    {
        ovs->time_base = vs->time_base;
        avcodec_parameters_copy(ovs->codecpar, vs->codecpar);
    }
    if (as)
    {
        oas->time_base = as->time_base;
        avcodec_parameters_copy(oas->codecpar, as->codecpar);
    }
  • 打开输出流 avio_open
    //打开输出流
    re = avio_open(&oc->pb, this->dstFilePath.toStdString().c_str(), AVIO_FLAG_WRITE);
    if (re < 0)
    {
        PrintError(re, "avio_open");
        return;
    }
  • 写入文件头部avformat_write_header
    //写入文件头
    re = avformat_write_header(oc, NULL);
    if (re < 0)
    {
        PrintError(re, "avformat_write_header");
        return;
    }
  • 循环while(true)解封装av_read_frame.并将解封装后的AVPacket通过av_interleaved_write_frame写入到本地文件
    while (isRunning)
    {
        //解封装
        re = av_read_frame(ic, &pkt);
        if (re != 0)
        {
            PrintError(re, "av_read_frame");
            break;
        }//写入音视频帧,此方法会自动清理AVPacket
        re = av_interleaved_write_frame(oc, &pkt);
        if (re != 0)
        {
            PrintError(re, "av_interleaved_write_frame");
        }
    }
  • 写入尾部av_write_trailer
    //写入尾部
    re = av_write_trailer(oc);
    if (re != 0)
    {
        PrintError(re, "av_write_trailer");
    }
  • 释放对象开辟的内存
    //关闭解封装输入上下文
    avformat_close_input(&ic);
    
    //关闭写入IO
    avio_closep(&oc->pb);
    //释放申请的输出解封装上下文
    avformat_free_context(oc);
    oc = nullptr;