音视频系列1:ffmpeg+rtmp拉流

音视频系列1:ffmpeg+rtmp拉流

  • 前言
  • 源码

前言

想不到又涉足到流媒体开发了,之前毕业设计也做过一个基于Linux的无线图传demo,是把mjpg-streamer精简化了,使用了http协议,这个demo是开源的,注释都写好了,清晰易懂。
有兴趣的见无线图传mjpg-streamer精简版(开源)

言归正传,我负责的部分是在linux下的拉流。本想直接抛弃ffmpeg这个大型怪物,单纯用个librtmp拉流,简单又方便,可是这个库好多年没更新了…用的openssl版本还是老版本,装了老半天装不上。暂时放弃,先做个demo出来,于是还是编译了ffmpeg。在百度逛了一圈,除了雷神的博客还值得一看- -其他的资料真的很少,我也不知道是不会搜还是怎么样。雷神的博客好是好,关键是有点久远了,不得不说现在的软件更新真的很快,ffmpeg换了一些api,还没入门的我也搞了一天,才把demo搞出来。

下图是流媒体标准传输框架,今天我们要实现绿色框部分。
音视频系列1:ffmpeg+rtmp拉流_第1张图片

源码

本篇博客是参考雷神的最简单的基于FFMPEG的推流器附件:收流器,修改API得以运行,并使用cmake方式生成可执行文件,解决了新手都会遇到的undefined reference to问题。

这个demo的功能是从芒果台接收视频流并转成flv格式保存下来,采用的协议是rtmp,停止运行按ctrl+c即可。

直接上源码吧(主要是还没搞懂hhh)!
main.cpp

#include 

//Linux...
#ifdef __cplusplus
extern "C"
{
#endif
#include 
#include 
#include 
#ifdef __cplusplus
}
#endif

using namespace std;
int main(int argc, char* argv[])
{
    AVOutputFormat *ofmt = NULL;
    //Input AVFormatContext and Output AVFormatContext
    AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
    AVPacket pkt;
    const char *in_filename, *out_filename;
    int ret, i;
    int videoindex=-1;
    int frame_index=0;
    in_filename  = "rtmp://58.200.131.2:1935/livetv/hunantv";
    out_filename = "receive.flv";
    av_register_all();
    //Network
    avformat_network_init();
    //Input
    if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
        printf( "Could not open input file.");
        goto end;
    }
    if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
        printf( "Failed to retrieve input stream information");
        goto end;
    }
    //? ctx=context
    for(i=0; i<ifmt_ctx->nb_streams; i++)
        if(ifmt_ctx->streams[i]->codecpar->codec_type==AVMEDIA_TYPE_VIDEO){
            videoindex=i;
            break;
        }

    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    //Output
    avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename); //RTMP
    if (!ofmt_ctx) {
        printf( "Could not create output context\n");
        ret = AVERROR_UNKNOWN;
        goto end;
    }
    ofmt = ofmt_ctx->oformat;
    for (i = 0; i < ifmt_ctx->nb_streams; i++) {
        //Create output AVStream according to input AVStream
        AVStream *in_stream = ifmt_ctx->streams[i];
        //AVStream *out_stream = avformat_new_stream(ofmt_ctx, in_stream->codec->codec);
        AVCodec *codec = avcodec_find_decoder(in_stream->codecpar->codec_id);
        AVStream *out_stream = avformat_new_stream(ofmt_ctx , codec);

        if (!out_stream) {
            printf( "Failed allocating output stream\n");
            ret = AVERROR_UNKNOWN;
            goto end;
        }

        AVCodecContext *p_codec_ctx = avcodec_alloc_context3(codec);
        ret = avcodec_parameters_to_context(p_codec_ctx , in_stream->codecpar);

        //Copy the settings of AVCodecContext
        if (ret < 0) {
            printf( "Failed to copy context from input to output stream codec context\n");
            goto end;
        }
        p_codec_ctx->codec_tag = 0;
        if (ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
            p_codec_ctx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
        ret = avcodec_parameters_from_context(out_stream->codecpar, p_codec_ctx);
        if(ret < 0){
            av_log(NULL , AV_LOG_ERROR , "eno:[%d] error to paramters codec paramter \n" , ret);
        }
    }

    //Dump Format------------------
    av_dump_format(ofmt_ctx, 0, out_filename, 1);
    //Open output URL
    if (!(ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
        if (ret < 0) {
            printf( "Could not open output URL '%s'", out_filename);
            goto end;
        }
    }
    //Write file header
    ret = avformat_write_header(ofmt_ctx, NULL);
    if (ret < 0) {
        printf( "Error occurred when opening output URL\n");
        goto end;
    }
    //拉流
    while (1) 
    {
        AVStream *in_stream, *out_stream;
        //Get an AVPacket
        ret = av_read_frame(ifmt_ctx, &pkt);
        if (ret < 0)
            break;

        in_stream  = ifmt_ctx->streams[pkt.stream_index];
        out_stream = ofmt_ctx->streams[pkt.stream_index];
        /* copy packet */
        //Convert PTS/DTS
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.pos = -1;
        //Print to Screen
        if(pkt.stream_index==videoindex){
            printf("Receive %8d video frames from input URL\n",frame_index);
            frame_index++;
        }
        ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
        if (ret < 0) {
            printf( "Error muxing packet\n");
            break;
        }
        av_packet_unref(&pkt);
    }

    //Write file trailer
    av_write_trailer(ofmt_ctx);
    end:
    avformat_close_input(&ifmt_ctx);
    /* close output */
    if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
        avio_close(ofmt_ctx->pb);
    avformat_free_context(ofmt_ctx);
    if (ret < 0 && ret != AVERROR_EOF) {
        printf( "Error occurred.\n");
        return -1;
    }
    return 0;
}

贴心的我已经帮大家准备好编译命令了,不管是直接编译还是cmake。
音视频系列1:ffmpeg+rtmp拉流_第2张图片
你可以直接编译:

 g++ main.cpp -o tests -lavcodec -lavformat -lavutil

也可以用Cmake:

CMakeLists.txt:解决undefined reference错误,参考此处

cmake_minimum_required(VERSION 3.10)
project(version1_0)

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_FLAGS "-D__STDC_CONSTANT_MACROS")

find_path(AVCODEC_INCLUDE_DIR libavcodec/avcodec.h)
find_library(AVCODEC_LIBRARY avcodec)

find_path(AVFORMAT_INCLUDE_DIR libavformat/avformat.h)
find_library(AVFORMAT_LIBRARY avformat)

find_path(AVUTIL_INCLUDE_DIR libavutil/avutil.h)
find_library(AVUTIL_LIBRARY avutil)

find_path(AVDEVICE_INCLUDE_DIR libavdevice/avdevice.h)
find_library(AVDEVICE_LIBRARY avdevice)

add_executable(version1_0 main.cpp )
target_include_directories(version1_0 PRIVATE ${AVCODEC_INCLUDE_DIR} ${AVFORMAT_INCLUDE_DIR} ${AVUTIL_INCLUDE_DIR} ${AVDEVICE_INCLUDE_DIR})
target_link_libraries(version1_0 PRIVATE ${AVCODEC_LIBRARY} ${AVFORMAT_LIBRARY} ${AVUTIL_LIBRARY} ${AVDEVICE_LIBRARY})

完结,妥妥的,除了雷神的博客和ffmpeg的api之外,我参考了ffmpeg里面的remuxing.c的exmple,它做的事情和我这个做的事情差不多。接下来就要靠大家深挖了!

接下来准备实现flv解析h.264–>YUV–>RGB,然后用opencv进行处理,请留意我下一篇博客。

如果我的文章对你有帮助,欢迎关注,点赞,评论。

你可能感兴趣的:(ffmpeg)