// A recording made into a regular MP4. While recording, the MP4 is written fragmented (encoder.jai), so // a crash still leaves one that plays; but a fragmented MP4's header says nothing of its length or // where its frames are, and one cut short by a crash never gets the index written at the end of a // clean stop, so players show its time and seek in it badly. record_remux copies its packets, as they // are, into a regular MP4: one header, with the length and the index of every frame, at the front // (faststart), so it plays, seeks and streams as any MP4 does. Nothing is encoded again. // Copies source's video and audio packets into a regular MP4 at destination. packets: how many. record_remux :: (source: string, destination: string) -> ok: bool, error: string, packets: s64 { av_log_set_level(AV_LOG_ERROR); input: *AVFormatContext; result := avformat_open_input(*input, temp_c_string(source), null, null); if result < 0 return false, av_error("FFmpeg cannot open the recording", result), 0; defer avformat_close_input(*input); result = avformat_find_stream_info(input, null); if result < 0 return false, av_error("FFmpeg cannot read the recording's streams", result), 0; output: *AVFormatContext; result = avformat_alloc_output_context2(*output, null, "mp4", temp_c_string(destination)); if result < 0 || !output return false, av_error("FFmpeg cannot make an MP4", result), 0; defer { if output.pb avio_closep(*output.pb); avformat_free_context(output); } // Each input stream's output stream, or -1 for one left out (neither video nor audio). map := NewArray(input.nb_streams, s32,, temp); for 0..input.nb_streams - 1 { in := input.streams[it]; map[it] = -1; if in.codecpar.codec_type != .VIDEO && in.codecpar.codec_type != .AUDIO continue; out := avformat_new_stream(output, null); if !out return false, "FFmpeg cannot add a stream", 0; avcodec_parameters_copy(out.codecpar, in.codecpar); out.codecpar.codec_tag = 0; // the MP4 muxer picks its own out.time_base = in.time_base; map[it] = out.index; } result = avio_open(*output.pb, temp_c_string(destination), AVIO_FLAG_WRITE); if result < 0 return false, av_error(tprint("FFmpeg cannot write %", destination), result), 0; options: *AVDictionary; av_dict_set(*options, "movflags", "+faststart", 0); // the header before the frames: a second pass at the end result = avformat_write_header(output, *options); av_dict_free(*options); if result < 0 return false, av_error("FFmpeg cannot start the MP4", result), 0; packet := av_packet_alloc(); defer av_packet_free(*packet); packets: s64; while av_read_frame(input, packet) >= 0 { // to the end, or to where a crash cut it index := map[packet.stream_index]; if index < 0 { av_packet_unref(packet); continue; } av_packet_rescale_ts(packet, input.streams[packet.stream_index].time_base, output.streams[index].time_base); packet.stream_index = index; packet.pos = -1; result = av_interleaved_write_frame(output, packet); // takes the packet's data if result < 0 return false, av_error("FFmpeg cannot write to the MP4", result), packets; packets += 1; } if !packets return false, "the recording holds no frames", 0; result = av_write_trailer(output); if result < 0 return false, av_error("FFmpeg cannot finish the MP4", result), packets; return true, "", packets; }