Reading an RTSP stream and remuxing to FLV with FFmpeg in C++
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.