1. 程式人生 > 其它 >FFmpeg菜雞互啄#第7篇#檔案/rtsp推流到rtmp

FFmpeg菜雞互啄#第7篇#檔案/rtsp推流到rtmp

關鍵步驟

    avformat_open_input//開啟輸入檔案/rtsp
    avformat_find_stream_info////獲取音視訊流資訊
    avformat_alloc_output_context2//建立輸出上下文
    avformat_new_stream//建立輸出流
    avcodec_copy_context//複製配置輸出流
    avio_open//開啟io
    avformat_write_header//寫入頭資訊
    av_interleaved_write_frame//推流幀
    av_write_trailer(octx);//寫檔案尾

大致過程:開啟(/配置)輸入、建立輸出(上下文/流)、配置輸出流、開啟輸出IO、迴圈推流。推流本地檔案的時候要計算一下pts、dts,並作延時推送。

Code

#include <stdio.h>

extern "C"
{
#include "libavformatavformat.h"
#include "libavutiltime.h"
}

#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avutil.lib")
#pragma comment(lib, "avcodec.lib")

int Error(int res)
{
    char buf[1024] = { 0 };
    av_strerror(res, buf, sizeof(buf));
    printf("error : %s.n", buf);
    return res;
}

int main(int argc, char* argv[])
{
    char* inUrl = "rtsp://184.72.239.149/vod/mp4://BigBuckBunny_175k.mov";//可以是本地檔案
    char* outUrl = "rtmp://123.207.71.137/live/test";

    //初始化所有封裝器
    av_register_all();

    //初始化網路庫
    avformat_network_init();

    int res = 0;
    //開啟檔案,解封裝檔案頭
    //輸入封裝上下文
    AVFormatContext* ictx = NULL;
    //設定rtsp協議延時最大值
    AVDictionary *opts = NULL;
    av_dict_set(&opts, "max_delay", "500", 0);
    if ((res = avformat_open_input(&ictx, inUrl, NULL, &opts)) != 0)
        return Error(res);

    //獲取音視訊流資訊
    if ((res = avformat_find_stream_info(ictx, NULL)) < 0)
        return Error(res);
    av_dump_format(ictx, 0, inUrl, 0);

    //建立輸出上下文
    AVFormatContext* octx = NULL;
    if ((res = avformat_alloc_output_context2(&octx, NULL, "flv", outUrl) < 0))
        return Error(res);

    //配置輸出流
    //遍歷輸入的AVStream
    for (int i = 0; i < ictx->nb_streams; ++i)
    {
        //建立輸出流
        AVStream* out = avformat_new_stream(octx, ictx->streams[i]->codec->codec);
        if (out == NULL)
        {
            printf("new stream error.n");
            return -1;
        }
        //複製配置資訊
        if ((res = avcodec_copy_context(out->codec, ictx->streams[i]->codec)) != 0)
            return Error(res);
        //out->codec->codec_tag = 0;//標記不需要重新編解碼
    }
    av_dump_format(octx, 0, outUrl, 1);

    //rtmp推流
    //開啟io
    //@param s Used to return the pointer to the created AVIOContext.In case of failure the pointed to value is set to NULL.
    res = avio_open(&octx->pb, outUrl, AVIO_FLAG_WRITE);
    if (octx->pb == NULL)
        return Error(res);

    //寫入頭資訊
    //avformat_write_header可能會改變流的timebase
    if ((res = avformat_write_header(octx, NULL)) < 0)
        return Error(res);

    long long  begintime = av_gettime();
    long long  realdts = 0;
    long long  caldts = 0;
    AVPacket pkt;
    while (true)
    {
        if ((res = av_read_frame(ictx, &pkt)) != 0)
            break;
        if(pkt.size <= 0)//讀取rtsp時pkt.size可能會等於0
            continue;
        //轉換pts、dts、duration
        pkt.pts = pkt.pts * av_q2d(ictx->streams[pkt.stream_index]->time_base) / av_q2d(octx->streams[pkt.stream_index]->time_base);
        pkt.dts = pkt.dts * av_q2d(ictx->streams[pkt.stream_index]->time_base) / av_q2d(octx->streams[pkt.stream_index]->time_base);
        pkt.duration = pkt.duration * av_q2d(ictx->streams[pkt.stream_index]->time_base) / av_q2d(octx->streams[pkt.stream_index]->time_base);
        pkt.pos = -1;//byte position in stream, -1 if unknown

        //檔案推流計算延時
        //av_usleep(30 * 1000);
        /*realdts = av_gettime() - begintime;
        caldts = 1000 * 1000 * pkt.pts * av_q2d(octx->streams[pkt.stream_index]->time_base);
        if (caldts > realdts)
            av_usleep(caldts - realdts);*/

        if ((res = av_interleaved_write_frame(octx, &pkt)) < 0)//推流,推完之後pkt的pts,dts竟然都被重置了!而且前面幾幀還因為dts沒有增長而返回-22錯誤
            Error(res);

        av_free_packet(&pkt);//回收pkt內部分配的記憶體
    }
    av_write_trailer(octx);//寫檔案尾

    system("pause");
    return 0;
}

Github

https://github.com/gongluck/FFmpegTest.git