嵌入式设备基于libav实现双输出:流媒体推流+无损高帧率文件存储
Great job getting that fragmented MPEG4 livestream up and running with libav on your embedded device! Let's break down the most efficient way to generate your two outputs from a single input—no redundant decoding, which is critical for resource-constrained embedded hardware.
Core Optimal Approach: Single Decode, Dual Encode/Output
The key win here is reusing the decoded frames from your input instead of spinning up two separate decoding pipelines. This cuts down on CPU, memory, and power usage—huge for embedded systems. Here's how to implement it step by step:
1. Reuse Your Existing Input/Decoding Setup
You already have the input stream opened, video stream identified, and decoder initialized. Keep this intact—we'll feed the decoded AVFrames from this pipeline to two separate encoder/output contexts.
2. Initialize Two Independent Output Contexts
Set up each output with its own format, encoder, and destination:
- Web Lossy Stream (Callback Push)
- Stick with fragmented MP4 for compatibility, or switch to RTMP/HLS if your target supports it.
- Use a lossy encoder (like H.264) with Web-friendly parameters: set a reasonable CRF (e.g., 22-25), fast preset, and appropriate bitrate.
- Configure your custom I/O callback via
avio_open2to handle pushing frames to your streaming endpoint.
- Lossless High-Frame-Rate File
- Choose a encoder optimized for lossless storage: FFV1 (great compression ratio, widely supported) or H.264 with
crf 0(hardware-friendly if your platform supports it). - Use MKV as the container (better support for lossless codecs and variable frame rates than MP4) or MOV if you need MP4-compatible lossless.
- Match the input's frame rate and resolution exactly to preserve all detail.
- Choose a encoder optimized for lossless storage: FFV1 (great compression ratio, widely supported) or H.264 with
3. Share Decoded Frames Between Outputs
For each decoded AVFrame from your input, create a reference to it (using av_frame_ref) for each encoder. This avoids copying frame data (saves memory!) while ensuring each encoder has its own writable reference.
4. Sync Timestamps & Handle Encoder Output
- Rescale the frame's PTS/DTS from the decoder's time base to each output stream's time base to avoid playback sync issues.
- For each encoder, send the referenced frame, retrieve the encoded packet, and write it to either your callback stream or the file.
Example Code Snippet (Simplified)
// Your existing input/decoder setup (already working) AVFormatContext *input_ctx = ...; AVCodecContext *dec_ctx = ...; AVFrame *decoded_frame = av_frame_alloc(); // --- Output 1: Web Lossy Stream (Callback) --- AVFormatContext *stream_ctx = avformat_alloc_context(); stream_ctx->oformat = av_guess_format("mp4", NULL, NULL); stream_ctx->oformat->flags |= AVFMT_FLAG_FRAGMENTED; // Keep fragmented MP4 // Hook up your custom push callback AVIOContext *avio_cb = NULL; avio_open2(&avio_cb, "stream://", AVIO_FLAG_WRITE, NULL, &your_cb_options); stream_ctx->pb = avio_cb; // Initialize H.264 lossy encoder AVStream *stream_out = avformat_new_stream(stream_ctx, NULL); avcodec_parameters_copy(stream_out->codecpar, dec_ctx->codecpar); stream_out->codecpar->codec_id = AV_CODEC_ID_H264; stream_out->codecpar->bit_rate = 2000000; // 2Mbps for Web AVCodecContext *stream_enc_ctx = avcodec_alloc_context3(avcodec_find_encoder(AV_CODEC_ID_H264)); stream_enc_ctx->crf = 23; stream_enc_ctx->preset = "fast"; stream_enc_ctx->time_base = av_inv_q(stream_out->r_frame_rate); avcodec_open2(stream_enc_ctx, NULL, NULL); // --- Output 2: Lossless File --- AVFormatContext *file_ctx = NULL; avformat_alloc_output_context2(&file_ctx, NULL, "mkv", "lossless_recording.mkv"); // Initialize FFV1 lossless encoder AVStream *file_out = avformat_new_stream(file_ctx, NULL); avcodec_parameters_copy(file_out->codecpar, dec_ctx->codecpar); file_out->codecpar->codec_id = AV_CODEC_ID_FFV1; AVCodecContext *file_enc_ctx = avcodec_alloc_context3(avcodec_find_encoder(AV_CODEC_ID_FFV1)); file_enc_ctx->flags |= AV_CODEC_FLAG_QSCALE; file_enc_ctx->global_quality = 0; // Lossless setting file_enc_ctx->time_base = av_inv_q(file_out->r_frame_rate); avcodec_open2(file_enc_ctx, NULL, NULL); // Open both outputs avformat_write_header(stream_ctx, NULL); avformat_write_header(file_ctx, NULL); // --- Main Processing Loop --- AVPacket input_pkt = {0}; while (av_read_frame(input_ctx, &input_pkt) == 0) { if (input_pkt.stream_index == your_video_stream_idx) { // Decode the input packet avcodec_send_packet(dec_ctx, &input_pkt); while (avcodec_receive_frame(dec_ctx, decoded_frame) == 0) { // --- Process Web Stream Output --- AVPacket stream_pkt = {0}; AVFrame *stream_frame = av_frame_alloc(); av_frame_ref(stream_frame, decoded_frame); avcodec_send_frame(stream_enc_ctx, stream_frame); while (avcodec_receive_packet(stream_enc_ctx, &stream_pkt) == 0) { av_packet_rescale_ts(&stream_pkt, dec_ctx->time_base, stream_out->time_base); av_interleaved_write_frame(stream_ctx, &stream_pkt); av_packet_unref(&stream_pkt); } av_frame_unref(stream_frame); av_frame_free(&stream_frame); // --- Process Lossless File Output --- AVPacket file_pkt = {0}; AVFrame *file_frame = av_frame_alloc(); av_frame_ref(file_frame, decoded_frame); avcodec_send_frame(file_enc_ctx, file_frame); while (avcodec_receive_packet(file_enc_ctx, &file_pkt) == 0) { av_packet_rescale_ts(&file_pkt, dec_ctx->time_base, file_out->time_base); av_interleaved_write_frame(file_ctx, &file_pkt); av_packet_unref(&file_pkt); } av_frame_unref(file_frame); av_frame_free(&file_frame); } } av_packet_unref(&input_pkt); } // Cleanup av_write_trailer(stream_ctx); av_write_trailer(file_ctx); // Free all contexts, frames, packets here (omitted for brevity)
Key Tips for Embedded Optimization
- Hardware Acceleration: If your platform supports it (e.g., VA-API, Rockchip VPU, NVIDIA NVENC), use hardware decoding/encoding to offload CPU work.
- Frame Reuse: Avoid allocating new
AVFrames for every encode—reuse a small pool of frames to reduce memory fragmentation. - Encoder Presets: For the Web stream, use the fastest preset your quality allows (e.g.,
ultrafastfor H.264) to minimize CPU load. - Error Handling: Add checks for
avcodec_send_frame/avcodec_receive_packetreturn codes to handle encoder backpressure (critical if your embedded CPU can't keep up with both encodes).
This approach is far more efficient than running two separate pipelines—you only decode once, and split the workload at the frame level, which is perfect for resource-limited embedded devices.
内容的提问来源于stack exchange,提问作者user2333829

