ffmpeg将视频流录制下来并保存到本地

一、概述

  保存网络流为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;

     

posted on 2025-05-28 11:54  飘杨......  阅读(227)  评论(0)    收藏  举报