将RTSP流保存为本地TS文件

Posted SanShuiGeGe

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了将RTSP流保存为本地TS文件相关的知识,希望对你有一定的参考价值。

1、功能:将RTSP流保存为本地TS文件

2、存在问题:
保存mp4文件播放不了,还未解决…希望路过的大佬帮忙瞅瞅 _

3、流程:
0)初始化:并注册所有的解封装器、封装器和协议,初始化网络库;
1)打开输入的解封装上下文;
2)打开输出的封装上下文;
3)为输出的封装上下文分别新建音频流、视频流stream;
4)根据输入编码类型获取输出编码器codec;
5)为输出流的编码器参数字段codecpar设置参数(从输入编码器参数字段拷贝);
6)创建输出的编码器上下文,并通过输出编码器参数字段设置编码器上下文;
7)打开输出的编码器上下文;
8)通过输出封装上下文写文件头;
9)读取输入解封装上下文的包packet
10)根据输入的解封装上下文和输出的封装上下文的时间基准time_base,转换时间戳pts、dts,以及设置整个时间duration;
11)将音视频包以正确交织方式写入输出的封装上下文

4、代码:

/*
功能:将RTSP保存为本地ts文件
存在问题:保存mp4文件播放不了,还未解决...
*/

#include <iostream>
#include <string>
extern "C"
    #include <libavformat/avformat.h>
    #include <libavcodec/avcodec.h>

using namespace std;
AVFormatContext *inputContext = NULL;
AVFormatContext *outputContext = NULL;

// 打开输入rtsp,获得输入封装上下文
bool openInput(string& inputUrl)

    // 输入的解封装上下文
    int ret = avformat_open_input(
        &inputContext,
        inputUrl.c_str(),
        0,  // 0表示自动选择解封器
        0 //参数设置,比如rtsp的延时时间
    );
    if (ret != 0)
    
        char buf[1024] =  0 ;
        av_strerror(ret, buf, sizeof(buf) - 1);
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "open " << inputUrl << " failed! :" << buf << endl;
        return false;
    

    ret = avformat_find_stream_info(inputContext, 0);
    if (ret < 0)
    
        char buf[1024] =  0 ;
        av_strerror(ret, buf, sizeof(buf) - 1);
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avformat_find_stream_info failed! " << buf << endl;
        return false;
    
    return true;


// 读取输入封装上下文的packet
AVPacket * readPacketFromSource()

    AVPacket* pkt = av_packet_alloc();
    int ret = av_read_frame(inputContext, pkt);
    if (ret != 0)
    
        av_packet_free(&pkt);
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "av_read_frame failed!" << endl;
        return NULL;
    

    return pkt;


// 修改时间戳
void av_packet_rescale_ts(AVPacket *pkt, AVRational src_tb, AVRational dst_tb)

    if(NULL == pkt)
    
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "input err!" << endl;
        return;
    
    if(pkt->pts != AV_NOPTS_VALUE)
        pkt->pts = av_rescale_q(pkt->pts, src_tb, dst_tb);
    if(pkt->dts != AV_NOPTS_VALUE)
        pkt->dts = av_rescale_q(pkt->dts, src_tb, dst_tb);
//    if(pkt->pts != AV_NOPTS_VALUE)
//        pkt->pts = av_rescale_q_rnd(pkt->pts, src_tb, dst_tb, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
//    if(pkt->dts != AV_NOPTS_VALUE)
//        pkt->dts = av_rescale_q_rnd(pkt->dts, src_tb, dst_tb, (enum AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
    if(pkt->duration > 0)
        pkt->duration = av_rescale_q(pkt->duration, src_tb, dst_tb);

//    pkt->pts = pkt->pts < pkt->dts ? pkt->dts:pkt->pts;
    pkt->pos = -1;


// 将packt写入新的封装上下文,并释放packet
bool writePacket(AVPacket *pkt)

    if(NULL == pkt || NULL == inputContext || NULL == outputContext)
    
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "input err!" << endl;
        return false;
    
    auto inputStream  = inputContext->streams[pkt->stream_index];
    auto outputStream = outputContext->streams[pkt->stream_index];
    av_packet_rescale_ts(pkt, inputStream->time_base, outputStream->time_base);
    // 将音视频包以正确交织方式写入封装上下文
    int ret = av_interleaved_write_frame(outputContext, pkt);
    // 释放!
    av_packet_free(&pkt);
    if(ret != 0)
    
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "av_interleaved_write_frame failed!" << endl;
        char buf[1024] =  0 ;
        av_strerror(ret, buf, sizeof(buf) - 1);
        cout << "avcodec_open2  failed! :" << buf << endl;
        return false;
    

    return true;


// 打开输出文件,并获取输出封装上下文
bool openOutput(string& url)

    // 根据输出封装格式,创建输出上下文
    int ret = avformat_alloc_output_context2(&outputContext, NULL, NULL, url.c_str());
    if(ret < 0)
    
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avformat_alloc_output_context2 failed!" << endl;
        return false;
    
    // 打开输出文件
    ret = avio_open2(&outputContext->pb, url.c_str(), AVIO_FLAG_READ_WRITE, NULL, NULL);
    if(ret < 0)
    
        avformat_free_context(outputContext);
        outputContext = NULL;
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avio_open failed!" << endl;
        return false;
    
    unsigned int i = 0;
    for(i = 0; i < inputContext->nb_streams; i++)
    
#if 0 // 旧版本,使用streams[i]->codec,不建议使用
        AVStream *stream = avformat_new_stream(outputContext,inputContext->streams[i]->codec->codec);
        ret = avcodec_copy_context(stream->codec, inputContext->streams[i]->codec);
        if(ret != 0)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avcodec_copy_context failed!" << endl;
            break;
        
#else // 新版本,弃用streams[i]->codec
        AVCodecParameters *inCodecPara = inputContext->streams[i]->codecpar;
        // 为封装上下文创建新的流
        AVStream *outStream = avformat_new_stream(outputContext, avcodec_find_decoder(inCodecPara->codec_id));
        if(NULL == outStream)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avformat_new_stream failed!" << endl;
            break;
        
        outStream->time_base.den=25;//AVRational这个结构标识一个分数,num为分数,den为分母(时间的刻度)
        outStream->time_base.num=1;
        // 找到和输入一样的编码器
        AVCodec* outCodec = avcodec_find_decoder(inCodecPara->codec_id);
        if (!outCodec)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "can't find the codec id " << inCodecPara->codec_id << endl;
            break;
        
        // 创建输出编码器上下文
        AVCodecContext *outCodecContext = avcodec_alloc_context3(outCodec);
        if(NULL == outCodecContext)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avcodec_alloc_context3 failed!" << endl;
            break;
        
        // 输入编码器参数 拷贝到 输出编码器参数
        AVCodecParameters *outCodecPara = outStream->codecpar;
        ret = avcodec_parameters_copy(outCodecPara, inCodecPara);
        if(ret < 0)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avcodec_parameters_copy failed!" << endl;
            break;
        

        // 不加这个,有的流保存为mp4会报错,[mp4 @ 031be9c0] Tag [27][0][0][0] incompatible with output codec id '27' (avc1)
        // 但是加了这个,保存成Mmp4还是播放不了...
        outCodecPara->codec_tag = 0;
        // 输出编码器参数 拷贝到 输出编码器上下文
        ret = avcodec_parameters_to_context(outCodecContext, outCodecPara);
        if(ret < 0)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avcodec_parameters_copy failed!" << endl;
            break;
        

//        outCodecContext->bit_rate =0;//目标的码率,即采样的码率;显然,采样码率越大,视频大小越大  比特率
//        outCodecContext->time_base.num=1;//下面两行:一秒钟25帧
//        outCodecContext->time_base.den=15;
//        outCodecContext->frame_number=1;//每包一个视频帧
        // 打开编码器输出上下文
        ret = avcodec_open2(outCodecContext, outCodec, 0);
        if(ret != 0)
        
            cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avcodec_open2 failed!" << endl;
            break;
        
        av_free(outCodec);
        outCodec = NULL;
#endif
    
    // 判断音视频流stream是否全部创建成功
    if(i != outputContext->nb_streams)
    
        avio_close(outputContext->pb);
        avformat_free_context(outputContext);
        outputContext = NULL;
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "failed!" << endl;
        return false;
    
    av_dump_format(outputContext, 0, url.c_str(), 1);//输出视频信息
    // 写入文件头
    ret = avformat_write_header(outputContext, NULL);
    if(ret < 0)
    
        avio_close(outputContext->pb);
        avformat_free_context(outputContext);
        outputContext = NULL;
        cout << "[" << __FILE__ << "|" << __LINE__ << "]" << "avformat_write_header failed!" << endl;
        char buf[1024] =  0 ;
        av_strerror(ret, buf, sizeof(buf) - 1);
        cout << "avformat_write_header  failed! :" << buf << endl;
        return false;
    

    return true;


void init()

    av_register_all();
    avformat_network_init();

void deinit()

    avformat_network_deinit();

int main()

    cout << "Hello World!" << endl;

    init();
    string inUrl = "http://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear2/prog_index.m3u8";
//    inUrl = "rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mp4";
    int ret = openInput(inUrl);
    if(true != ret)
        return -1;
    string outUrl = "./test1.mp4";
    ret =openOutput(outUrl);
    if(true != ret)
        return -1;
    while(1)
    
        auto pkt = readPacketFromSource();
        if(pkt)
        
            ret = writePacket(pkt);
            if(ret)
                cout << "write success!" << endl;
            else
                cout << "write fail!" << endl;
        
    
    if(outputContext)
    
        avio_close(outputContext->pb);
        avformat_free_context(outputContext);
    
    if(inputContext)
    
        avformat_free_context(inputContext);
    
    deinit();
    return 0;

将RTSP流保存到android中的mp4文件

我正在开发一个项目,我需要从IPCamera读取输入流。我可以通过rtsp url获取此信息。

显示IP摄像机流。我可以通过使用 -

    videoView = (VideoView) this.findViewById(R.id.videoView1);
    MediaController mc = new MediaController(this);
    videoView.setMediaController(mc);
    videoView.setVideoURI(Uri.parse("rtsp://xxxxxxxx/camera1"));
    videoView.requestFocus();

现在我想将此流记录到MP4文件中。同样我使用mediarecorder.Here我被卡住了。

    MediaRecorder mediaRecorder = new MediaRecorder();
    //mediaRecorder.setAudioSource(MediaRecorder.AudioSource.DEFAULT);
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.DEFAULT);
    mediaRecorder.setOutputFile("rtsp://xxxxxxxxx/camera1");
    try {
        mediaRecorder.prepare();
    } catch (IllegalStateException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    } catch (IOException e) {
        // TODO Auto-generated catch block
        e.printStackTrace();
    }
    mediaRecorder.start();

谢谢

答案

使用ffmpeg

  1. 添加ffmpeg依赖项。 compile 'nl.bravobit:android-ffmpeg:1.1.5'
  2. 记录流。 String RTSP_URL = "rtsp://<your_rtsp_url>"; final File targetFile = new File( getExternalStoragePublicDirectory( Environment.DIRECTORY_MOVIES ) + "/recording1.mp4" ); final FFmpeg ffmpeg = FFmpeg.getInstance(this); String[] ffmpegCommand = new String[]{ "-i", RTSP_URL, "-acodec", "copy", "-vcodec", "copy", targetFile.toString() }; final FFtask ffTask = ffmpeg.execute( ffmpegCommand, new FFcommandExecuteResponseHandler() { @Override public void onStart() {} @Override public void onProgress(String message) {} @Override public void onFailure(String message) {} @Override public void onSuccess(String message) {} @Override public void onFinish() {} } ); final Timer timer = new java.util.Timer(); TimerTask timerTask = new TimerTask() { @Override public void run() { ffTask.sendQuitSignal(); } }; timer.schedule( timerTask, 30000 ); // Will stop recording after 30 seconds.

以上是关于将RTSP流保存为本地TS文件的主要内容,如果未能解决你的问题,请参考以下文章

JavaCV音视频开发宝典:rtsp转推到rtp(非TS流方式),及使用TS流发送解决sdp缺失问题

JavaCV音视频开发宝典:rtsp转推到rtp(非TS流方式),及使用TS流发送解决sdp缺失问题

RTSP在线视频环境搭建

如何用ffmpeg将rtsp视频流录制成mp4文件?

VLC拉网络流保存成本地视频

ffmpeg接收rtsp流问题