Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Reading an RTSP stream and remuxing to FLV with FFmpeg in C++

Tech 2

The program below opens an RTSP source, probes its streams, and remuxes packets direct to an FLV file without decoding. Timestamps are rescaled to the destination time base and packets are interleaved before writing.

  • Initialize FFmpeg networking and open the input with RTSP options (e.g., TCP transport, max_delay).
  • Inspect input streams and build a stream index mapping.
  • Create the output context, mirror supported streams, and copy codec parameters.
  • Open the output IO and write the header.
  • Read packets, rewrite stream index, rescale PTS/DTS/duration, and write using av_interleaved_write_frame.
  • Write trailer and release resources.
// Build: link against avformat, avcodec, avutil
// Example: g++ -std=c++11 rtsp_remux.cpp -lavformat -lavcodec -lavutil -o rtsp_remux

#ifndef INT64_C
#  define INT64_C(c)  (c##LL)
#  define UINT64_C(c) (c##ULL)
#endif

extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/avutil.h>
#include <libavutil/error.h>
}

#include <cstdio>
#include <vector>

static const char* ff_err(int e) {
    static char buf[AV_ERROR_MAX_STRING_SIZE];
    av_strerror(e, buf, sizeof(buf));
    return buf;
}

int main() {
    const char* input_url  = "rtsp://user:password@192.168.0.64:554/Streaming/Channels/101"; // replace as needed
    const char* output_url = "output.flv";

    AVFormatContext* inCtx  = nullptr;
    AVFormatContext* outCtx = nullptr;
    AVDictionary* inOpts    = nullptr;

    // Configure RTSP options
    av_dict_set(&inOpts, "rtsp_transport", "tcp", 0);
    av_dict_set(&inOpts, "max_delay",      "5000000", 0); // microseconds
    // Optional: av_dict_set(&inOpts, "stimeout", "5000000", 0); // microseconds for some builds

    avformat_network_init();

    int ret = avformat_open_input(&inCtx, input_url, nullptr, &inOpts);
    if (ret < 0) {
        std::fprintf(stderr, "open_input failed: %s\n", ff_err(ret));
        goto cleanup;
    }

    ret = avformat_find_stream_info(inCtx, nullptr);
    if (ret < 0) {
        std::fprintf(stderr, "find_stream_info failed: %s\n", ff_err(ret));
        goto cleanup;
    }

    av_dump_format(inCtx, 0, input_url, 0);

    // Map input stream indices -> output stream indices (-1 means drop)
    std::vector<int> stream_map(inCtx->nb_streams, -1);

    // Find a representative video stream for simple logging
    int best_video = av_find_best_stream(inCtx, AVMEDIA_TYPE_VIDEO, -1, -1, nullptr, 0);

    // Create output context (muxer inferred from filename)
    ret = avformat_alloc_output_context2(&outCtx, nullptr, nullptr, output_url);
    if (ret < 0 || !outCtx) {
        std::fprintf(stderr, "alloc_output_context failed: %s\n", ff_err(ret));
        goto cleanup;
    }

    // Create corresponding output streams and copy codec parameters
    for (unsigned i = 0; i < inCtx->nb_streams; ++i) {
        AVStream* ist = inCtx->streams[i];
        AVCodecParameters* ip = ist->codecpar;

        // Only pass through audio/video/subtitle; drop others
        if (ip->codec_type != AVMEDIA_TYPE_VIDEO &&
            ip->codec_type != AVMEDIA_TYPE_AUDIO &&
            ip->codec_type != AVMEDIA_TYPE_SUBTITLE) {
            stream_map[i] = -1;
            continue;
        }

        AVStream* ost = avformat_new_stream(outCtx, nullptr);
        if (!ost) {
            std::fprintf(stderr, "avformat_new_stream failed\n");
            ret = AVERROR(ENOMEM);
            goto cleanup;
        }

        ret = avcodec_parameters_copy(ost->codecpar, ip);
        if (ret < 0) {
            std::fprintf(stderr, "parameters_copy failed: %s\n", ff_err(ret));
            goto cleanup;
        }
        ost->codecpar->codec_tag = 0; // let muxer decide

        // Preserve input time base as a hint; muxer may override on header write
        ost->time_base = ist->time_base;

        stream_map[i] = static_cast<int>(ost->index);
    }

    av_dump_format(outCtx, 0, output_url, 1);

    if (!(outCtx->oformat->flags & AVFMT_NOFILE)) {
        ret = avio_open(&outCtx->pb, output_url, AVIO_FLAG_WRITE);
        if (ret < 0) {
            std::fprintf(stderr, "avio_open failed: %s\n", ff_err(ret));
            goto cleanup;
        }
    }

    ret = avformat_write_header(outCtx, nullptr);
    if (ret < 0) {
        std::fprintf(stderr, "write_header failed: %s\n", ff_err(ret));
        goto cleanup;
    }

    // Packet remux loop
    {
        AVPacket pkt;
        av_init_packet(&pkt);
        long long video_frame_count = 0;

        for (;;) {
            ret = av_read_frame(inCtx, &pkt);
            if (ret < 0) {
                break; // EOF or error
            }

            int in_index = pkt.stream_index;
            int out_index = (in_index >= 0 && in_index < (int)stream_map.size()) ? stream_map[in_index] : -1;

            if (out_index < 0) {
                av_packet_unref(&pkt);
                continue; // stream dropped
            }

            AVStream* ist = inCtx->streams[in_index];
            AVStream* ost = outCtx->streams[out_index];

            // Rescale timestamps to the muxer time base
            pkt.stream_index = out_index;
            pkt.pts = av_rescale_q_rnd(pkt.pts, ist->time_base, ost->time_base,
                                       (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.dts = av_rescale_q_rnd(pkt.dts, ist->time_base, ost->time_base,
                                       (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
            pkt.duration = av_rescale_q(pkt.duration, ist->time_base, ost->time_base);
            pkt.pos = -1;

            if (best_video >= 0 && in_index == best_video) {
                std::printf("video frame %8lld\n", video_frame_count++);
            }

            ret = av_interleaved_write_frame(outCtx, &pkt);
            av_packet_unref(&pkt);
            if (ret < 0) {
                if (ret == AVERROR(EINVAL)) {
                    // Skip badly-timestamped packet and continue
                    continue;
                }
                std::fprintf(stderr, "write_frame failed: %s\n", ff_err(ret));
                break;
            }
        }
    }

    // Finalize file
    ret = av_write_trailer(outCtx);
    if (ret < 0) {
        std::fprintf(stderr, "write_trailer failed: %s\n", ff_err(ret));
    }

cleanup:
    av_dict_free(&inOpts);

    if (inCtx) {
        avformat_close_input(&inCtx);
    }

    if (outCtx) {
        if (!(outCtx->oformat->flags & AVFMT_NOFILE) && outCtx->pb) {
            avio_closep(&outCtx->pb);
        }
        avformat_free_context(outCtx);
    }

    avformat_network_deinit();

    if (ret < 0 && ret != AVERROR_EOF) {
        return 1;
    }

    return 0;
}

Notes

  • For RTSP over unreliable networks, consider adjusting options such as buffer_size, stimeout, fflags, or reordering flags depending on the camera/encoder behavier.
  • The code copies codec parameters and remuxes without decoding; if you need to transcode, create decoders/encoders and feed encoded packets from the encoder to the muxer instead of copying parameters.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.