/* muxavformat.c Copyright (c) 2003-2025 HandBrake Team This file is part of the HandBrake source code Homepage: . It may be used under the terms of the GNU General Public License v2. For full terms see the file COPYING file or visit http://www.gnu.org/licenses/gpl-2.0.html */ #include "libavcodec/bsf.h" #include "libavformat/avformat.h" #include "libavutil/avstring.h" #include #include "handbrake/handbrake.h" #include "handbrake/hbffmpeg.h" #include "handbrake/lang.h" struct hb_mux_data_s { enum { MUX_TYPE_VIDEO, MUX_TYPE_AUDIO, MUX_TYPE_SUBTITLE } type; AVFormatContext *oc; AVStream *st; int64_t duration; hb_buffer_t *delay_buf; int64_t prev_chapter_tc; int16_t current_chapter; AVBSFContext *bitstream_context; }; struct hb_mux_object_s { HB_MUX_COMMON; hb_job_t *job; AVFormatContext *oc; AVRational time_base; AVPacket *pkt; AVPacket *empty_pkt; int ntracks; hb_mux_data_t **tracks; }; enum { META_HB, META_MUX_MP4, META_MUX_MOV, META_MUX_MKV, META_MUX_WEBM, META_MUX_LAST }; const char *metadata_keys[][META_MUX_LAST] = { {"Name", "title", "com.apple.quicktime.displayname", "TITLE"}, {"Artist", "artist", "com.apple.quicktime.artist", "ARTIST"}, {"AlbumArtist", "album_artist", NULL, NULL}, {"Album", "album", "com.apple.quicktime.album", NULL}, {"Genre", "genre", "com.apple.quicktime.genre", "GENRE"}, {"ReleaseDate", "date", "com.apple.quicktime.creationdate", "DATE_RELEASED"}, {"CreationTime", "creation_time", NULL, NULL}, {"Track", "track", "com.apple.quicktime.track", NULL}, {"Show", "show", NULL, NULL}, {"Network", "network", NULL, NULL}, {"Episode ID", "episode_id", NULL, NULL}, {"Episode Sort", "episode_sort", NULL, NULL}, {"Season Number", "season_number", NULL, NULL}, {"Rating", "rating", NULL, NULL}, {"Media Type", "media_type", NULL, NULL}, {"HD Video", "hd_video", NULL, NULL}, {"Description", "description", "com.apple.quicktime.description", "DESCRIPTION"}, {"LongDescription", "synopsis", "com.apple.quicktime.information", "SYNOPSIS"}, {"SeriesDescription", "series_description", NULL, NULL}, {"Comment", "comment", "com.apple.quicktime.comment", "SUMMARY"}, {"Grouping", "grouping", NULL, NULL}, {"Compilation", "compilation", NULL, NULL}, {"Tempo", "tmpo", NULL, "BPM"}, {"Subtitle", "subtitle", NULL, "SUBTITLE"}, {"SongDescription", "song_description", NULL, NULL}, {"Director", "director", "com.apple.quicktime.director", "DIRECTOR"}, {"ArtDirector", "art_director", NULL, "DIRECTOR_OF_PHOTOGRAPHY"}, {"Composer", "composer", "com.apple.quicktime.composer", "COMPOSER"}, {"Arranger", "arranger", "com.apple.quicktime.arranger", "ARRANGER"}, {"Author", "author", "com.apple.quicktime.author", "WRITTEN_BY"}, {"Acknowledgement", "acknowledgement", NULL, NULL}, {"Conductor", "conductor", NULL, "CONDUCTOR"}, {"Lyrics", "lyrics", NULL, "LYRICS"}, {"Keywords", "keywords", "com.apple.quicktime.keywords", "KEYWORDS"}, {"Copyright", "copyright", "com.apple.quicktime.copyright", "COPYRIGHT"}, {"WorkName", "work_name", NULL, NULL}, {"MovementName", "movement_name", NULL, NULL}, {"MovementNumber", "movement_number", NULL, NULL}, {"MovementShow", "movement_count", NULL, NULL}, {"ShowWorkAndMovement", "show_work_and_movement", NULL, NULL}, {"LinearNotes", "linear_notes", NULL, NULL}, {"RecordCompany", "make", "com.apple.quicktime.make", NULL}, {"OriginalArtist", "original_artist", "com.apple.quicktime.originalartist", NULL}, {"PhonogramRights", "phonogram_rights", "com.apple.quicktime.phonogramrights", NULL}, {"Producer", "producer", "com.apple.quicktime.producer", "PRODUCER"}, {"Performers", "performers", "com.apple.quicktime.performer", NULL}, {"Publisher", "publisher", "com.apple.quicktime.publisher", "PUBLISHER"}, {"SoundEngineer", "sound_engineer", NULL, "SOUND_ENGINEER"}, {"Soloist", "soloist", NULL, "LEAD_PERFORMER"}, {"Credits", "original_source", "com.apple.quicktime.credits", NULL}, {"Thanks", "thanks", NULL, "THANKS_TO"}, {"OnlineExtra", "URL", NULL, NULL}, {"ExecutiveProducer", "executive_producer", NULL, "EXECUTIVE_PRODUCER"}, {"iTunesU", "itunes_u", NULL, NULL}, {"Podcast", "podcast", NULL, NULL}, {"Category", "category", NULL, NULL}, {"ContentID", "content_id", NULL, NULL}, {"ArtistID", "artist_id", NULL, NULL}, {"PlaylistID", "playlist_id", NULL, NULL}, {"GenreID", "genre_id", NULL, NULL}, {"ComposerID", "composer_id", NULL, NULL}, {"XID", "xid", NULL, NULL}, {"iTunEXTC", "iTunEXTC", NULL, NULL}, {"iTunMOVI", "iTunMOVI", NULL, NULL}, {"Location", "location", "com.apple.quicktime.location.ISO6709", NULL}, {NULL}}; static const char *lookup_meta_mux_key(int meta_mux, const char *hb_key) { int ii; for (ii = 0; metadata_keys[ii][META_HB] != NULL; ii++) { if (!strcmp(hb_key, metadata_keys[ii][META_HB])) { return metadata_keys[ii][meta_mux]; } } return NULL; } const char *hb_lookup_meta_key(const char *mux_key) { int ii, jj; for (ii = 0; metadata_keys[ii][META_HB] != NULL; ii++) { for (jj = 0; jj < META_MUX_LAST; jj++) { if (metadata_keys[ii][jj] != NULL && !strcmp(mux_key, metadata_keys[ii][jj])) { return metadata_keys[ii][META_HB]; } } } return NULL; } static char *lookup_lang_code(int mux, char *iso639_2) { iso639_lang_t *lang; char *out = NULL; switch (mux) { case HB_MUX_AV_MP4: case HB_MUX_AV_AVI: out = iso639_2; break; case HB_MUX_AV_MKV: case HB_MUX_AV_WEBM: // webm is a subset of mkv // MKV lang codes should be ISO-639-2B if it exists, // else ISO-639-2 lang = lang_for_code2(iso639_2); out = lang->iso639_2b && *lang->iso639_2b ? lang->iso639_2b : lang->iso639_2; break; default: break; } return out; } const char *get_subtitle_muxer(int source) { switch (source) { case CC608SUB: case CC708SUB: case SSASUB: case IMPORTSSA: return "ass"; case UTF8SUB: case TX3GSUB: case IMPORTSRT: return "srt"; case PGSSUB: return "sup"; } return NULL; } static int set_extradata(hb_data_t *extradata, uint8_t **priv_data, int *priv_size) { if (*priv_data) { av_freep(priv_data); *priv_size = 0; } if (extradata && extradata->size > 0) { // libavformat can over-read the buffer by up to 8 bytes // when it fills it's get_bits cache. // // So allocate extra bytes *priv_size = extradata->size; *priv_data = av_mallocz(extradata->size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("extradata: malloc failure"); return 1; } memcpy(*priv_data, extradata->bytes, extradata->size); } return 0; } /********************************************************************** * avformatInit ********************************************************************** * Allocates hb_mux_data_t structures, create file and write headers *********************************************************************/ static int avformatInit(hb_mux_object_t *m) { hb_job_t *job = m->job; hb_audio_t *audio; hb_mux_data_t *track; int meta_mux; int max_tracks; int ii, jj, ret; int clock_min, clock_max, clock; hb_video_framerate_get_limits(&clock_min, &clock_max, &clock); const char *muxer_name = NULL; uint8_t default_track_flag = 1; uint8_t need_fonts = 0; char *lang; m->pkt = av_packet_alloc(); m->empty_pkt = av_packet_alloc(); if (m->pkt == NULL || m->empty_pkt == NULL) { hb_error("muxavformat: av_packet_alloc failed"); goto error; } max_tracks = 1 + hb_list_count(job->list_audio) + hb_list_count(job->list_subtitle); m->tracks = calloc(max_tracks, sizeof(hb_mux_data_t *)); if (m->tracks == NULL) { hb_error("muxavformat: calloc failed"); goto error; } AVDictionary *av_opts = NULL; switch (job->mux) { case HB_MUX_AV_MP4: m->time_base.num = 1; m->time_base.den = 90000; if (job->ipod_atom) muxer_name = "ipod"; else muxer_name = "mp4"; meta_mux = META_MUX_MP4; av_dict_set(&av_opts, "brand", "mp42", 0); av_dict_set(&av_opts, "strict", "experimental", 0); if (job->optimize) av_dict_set(&av_opts, "movflags", "faststart+disable_chpl+write_colr", 0); else av_dict_set(&av_opts, "movflags", "+disable_chpl+write_colr", 0); break; case HB_MUX_AV_MKV: // libavformat is essentially hard coded such that it only // works with a timebase of 1/1000 m->time_base.num = 1; m->time_base.den = 1000; muxer_name = "matroska"; meta_mux = META_MUX_MKV; av_dict_set(&av_opts, "default_mode", "passthrough", 0); break; case HB_MUX_AV_WEBM: // libavformat is essentially hard coded such that it only // works with a timebase of 1/1000 m->time_base.num = 1; m->time_base.den = 1000; muxer_name = "webm"; meta_mux = META_MUX_WEBM; av_dict_set(&av_opts, "default_mode", "passthrough", 0); break; case HB_MUX_AV_AVI: m->time_base.num = 1; m->time_base.den = 90000; muxer_name = "avi"; meta_mux = META_MUX_MP4; break; default: { hb_error("Invalid Mux %x", job->mux); goto error; } } ret = avformat_alloc_output_context2(&m->oc, NULL, muxer_name, job->file); if (ret < 0) { hb_error("Could not initialize avformat context."); goto error; } ret = avio_open2(&m->oc->pb, job->file, AVIO_FLAG_WRITE, &m->oc->interrupt_callback, NULL); if (ret < 0) { if (ret == -2) { hb_error( "avio_open2 failed, errno -2: Could not write to indicated output " "file. Please check destination path and file permissions"); } else { hb_error("avio_open2 failed, errno %d", ret); } goto error; } /* Video track */ track = m->tracks[m->ntracks++] = calloc(1, sizeof(hb_mux_data_t)); job->mux_data = track; track->type = MUX_TYPE_VIDEO; track->prev_chapter_tc = AV_NOPTS_VALUE; track->st = avformat_new_stream(m->oc, NULL); if (track->st == NULL) { hb_error("Could not initialize video stream"); goto error; } track->st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; switch (job->vcodec) { case HB_VCODEC_X264_8BIT: case HB_VCODEC_X264_10BIT: case HB_VCODEC_VT_H264: case HB_VCODEC_FFMPEG_VCE_H264: case HB_VCODEC_FFMPEG_NVENC_H264: case HB_VCODEC_FFMPEG_QSV_H264: case HB_VCODEC_FFMPEG_MF_H264: track->st->codecpar->codec_id = AV_CODEC_ID_H264; if (job->mux == HB_MUX_AV_MP4 && job->inline_parameter_sets) { track->st->codecpar->codec_tag = MKTAG('a', 'v', 'c', '3'); } else { track->st->codecpar->codec_tag = MKTAG('a', 'v', 'c', '1'); } break; case HB_VCODEC_FFMPEG_MPEG4: track->st->codecpar->codec_id = AV_CODEC_ID_MPEG4; break; case HB_VCODEC_FFMPEG_MPEG2: track->st->codecpar->codec_id = AV_CODEC_ID_MPEG2VIDEO; break; case HB_VCODEC_FFMPEG_VP8: track->st->codecpar->codec_id = AV_CODEC_ID_VP8; break; case HB_VCODEC_FFMPEG_VP9: case HB_VCODEC_FFMPEG_VP9_10BIT: track->st->codecpar->codec_id = AV_CODEC_ID_VP9; break; case HB_VCODEC_SVT_AV1: case HB_VCODEC_SVT_AV1_10BIT: case HB_VCODEC_FFMPEG_NVENC_AV1: case HB_VCODEC_FFMPEG_NVENC_AV1_10BIT: case HB_VCODEC_FFMPEG_VCE_AV1: case HB_VCODEC_FFMPEG_MF_AV1: track->st->codecpar->codec_id = AV_CODEC_ID_AV1; break; case HB_VCODEC_FFMPEG_QSV_AV1_10BIT: case HB_VCODEC_FFMPEG_QSV_AV1: { const AVBitStreamFilter *bsf; AVBSFContext *ctx; int ret; track->st->codecpar->codec_id = AV_CODEC_ID_AV1; bsf = av_bsf_get_by_name("extract_extradata"); ret = av_bsf_alloc(bsf, &ctx); if (ret < 0) { hb_error("AV1 bitstream filter: alloc failure"); goto error; } track->bitstream_context = ctx; if (track->bitstream_context != NULL) { avcodec_parameters_copy(track->bitstream_context->par_in, track->st->codecpar); ret = av_bsf_init(track->bitstream_context); if (ret < 0) { hb_error("AV1 bitstream filter: init failure"); goto error; } } } break; case HB_VCODEC_THEORA: track->st->codecpar->codec_id = AV_CODEC_ID_THEORA; break; case HB_VCODEC_X265_8BIT: case HB_VCODEC_X265_10BIT: case HB_VCODEC_X265_12BIT: case HB_VCODEC_X265_16BIT: case HB_VCODEC_VT_H265: case HB_VCODEC_VT_H265_10BIT: case HB_VCODEC_FFMPEG_VCE_H265: case HB_VCODEC_FFMPEG_VCE_H265_10BIT: case HB_VCODEC_FFMPEG_NVENC_H265: case HB_VCODEC_FFMPEG_NVENC_H265_10BIT: case HB_VCODEC_FFMPEG_QSV_H265: case HB_VCODEC_FFMPEG_QSV_H265_10BIT: case HB_VCODEC_FFMPEG_MF_H265: track->st->codecpar->codec_id = AV_CODEC_ID_HEVC; if (job->mux == HB_MUX_AV_MP4 && job->inline_parameter_sets) { track->st->codecpar->codec_tag = MKTAG('h', 'e', 'v', '1'); } else { track->st->codecpar->codec_tag = MKTAG('h', 'v', 'c', '1'); } break; case HB_VCODEC_FFMPEG_FFV1: track->st->codecpar->codec_id = AV_CODEC_ID_FFV1; break; default: hb_error("muxavformat: Unknown video codec: %x", job->vcodec); goto error; } if (set_extradata(job->extradata, &track->st->codecpar->extradata, &track->st->codecpar->extradata_size)) { goto error; } track->st->sample_aspect_ratio.num = job->par.num; track->st->sample_aspect_ratio.den = job->par.den; track->st->codecpar->sample_aspect_ratio.num = job->par.num; track->st->codecpar->sample_aspect_ratio.den = job->par.den; track->st->codecpar->width = job->width; track->st->codecpar->height = job->height; track->st->codecpar->format = job->output_pix_fmt; track->st->disposition |= AV_DISPOSITION_DEFAULT; track->st->codecpar->color_primaries = hb_output_color_prim(job); track->st->codecpar->color_trc = hb_output_color_transfer(job); track->st->codecpar->color_space = hb_output_color_matrix(job); track->st->codecpar->color_range = job->color_range; track->st->codecpar->chroma_location = job->chroma_location; if (job->color_transfer == HB_COLR_TRA_SMPTEST2084) { if (job->mastering.has_primaries || job->mastering.has_luminance) { AVMasteringDisplayMetadata mastering = hb_mastering_hb_to_ff(job->mastering); uint8_t *mastering_data = av_malloc(sizeof(AVMasteringDisplayMetadata)); memcpy(mastering_data, &mastering, sizeof(AVMasteringDisplayMetadata)); av_packet_side_data_add(&track->st->codecpar->coded_side_data, &track->st->codecpar->nb_coded_side_data, AV_PKT_DATA_MASTERING_DISPLAY_METADATA, mastering_data, sizeof(AVMasteringDisplayMetadata), 0); } if (job->coll.max_cll && job->coll.max_fall) { AVContentLightMetadata coll; coll.MaxCLL = job->coll.max_cll; coll.MaxFALL = job->coll.max_fall; uint8_t *coll_data = av_malloc(sizeof(AVContentLightMetadata)); memcpy(coll_data, &coll, sizeof(AVContentLightMetadata)); av_packet_side_data_add(&track->st->codecpar->coded_side_data, &track->st->codecpar->nb_coded_side_data, AV_PKT_DATA_CONTENT_LIGHT_LEVEL, coll_data, sizeof(AVContentLightMetadata), 0); } } if (job->ambient.ambient_illuminance.num && job->ambient.ambient_illuminance.den) { AVAmbientViewingEnvironment ambient = hb_ambient_hb_to_ff(job->ambient); uint8_t *ambient_data = av_malloc(sizeof(AVAmbientViewingEnvironment)); memcpy(ambient_data, &ambient, sizeof(AVAmbientViewingEnvironment)); av_packet_side_data_add(&track->st->codecpar->coded_side_data, &track->st->codecpar->nb_coded_side_data, AV_PKT_DATA_AMBIENT_VIEWING_ENVIRONMENT, ambient_data, sizeof(AVAmbientViewingEnvironment), 0); } if (job->passthru_dynamic_hdr_metadata & HB_HDR_DYNAMIC_METADATA_DOVI) { if (job->dovi.dv_profile == 5 && job->mux == HB_MUX_AV_MP4) { if (track->st->codecpar->codec_id == AV_CODEC_ID_HEVC) { track->st->codecpar->codec_tag = MKTAG('d', 'v', 'h', '1'); } } AVDOVIDecoderConfigurationRecord dovi = hb_dovi_hb_to_ff(job->dovi); uint8_t *dovi_data = av_malloc(sizeof(AVDOVIDecoderConfigurationRecord)); memcpy(dovi_data, &dovi, sizeof(AVDOVIDecoderConfigurationRecord)); av_packet_side_data_add(&track->st->codecpar->coded_side_data, &track->st->codecpar->nb_coded_side_data, AV_PKT_DATA_DOVI_CONF, dovi_data, sizeof(AVDOVIDecoderConfigurationRecord), 0); m->oc->strict_std_compliance = FF_COMPLIANCE_UNOFFICIAL; } hb_rational_t vrate = job->vrate; hb_rational_t clock_vrate = {clock, av_rescale(vrate.den, clock, vrate.num)}; int standard_rate = 0; // Check if the vrate is similar to a standard rate that we have in our // hb_video_rates table. Because of rounding errors and approximations made // while measuring framerate, the actual value may not be exact. So we look // for rates that are "close" const hb_rate_t *video_framerate = NULL; while ((video_framerate = hb_video_framerate_get_next(video_framerate)) != NULL) { if (abs(clock_vrate.den - video_framerate->rate) < 10) { vrate.num = clock; vrate.den = video_framerate->rate; standard_rate = 1; break; } } hb_reduce(&vrate.num, &vrate.den, vrate.num, vrate.den); if (job->mux == HB_MUX_AV_MP4 && standard_rate && job->cfr == 1 && vrate.den * 90000L % vrate.num) { // Set the the correct video time base to avoid // timestamps jitter when using NTSC framerates track->st->time_base.num = vrate.den; track->st->time_base.den = vrate.num; } else { track->st->time_base = m->time_base; } track->st->avg_frame_rate.num = vrate.num; track->st->avg_frame_rate.den = vrate.den; /* add the audio tracks */ for (ii = 0; ii < hb_list_count(job->list_audio); ii++) { audio = hb_list_item(job->list_audio, ii); track = m->tracks[m->ntracks++] = calloc(1, sizeof(hb_mux_data_t)); audio->priv.mux_data = track; track->type = MUX_TYPE_AUDIO; track->st = avformat_new_stream(m->oc, NULL); if (track->st == NULL) { hb_error("Could not initialize audio stream"); goto error; } track->st->codecpar->codec_type = AVMEDIA_TYPE_AUDIO; track->st->codecpar->initial_padding = audio->priv.init_delay * audio->config.out.samplerate / 90000; track->st->codecpar->frame_size = audio->config.out.samples_per_frame; if (job->mux == HB_MUX_AV_MP4) { track->st->time_base.num = 1; track->st->time_base.den = audio->config.out.samplerate; } else { track->st->time_base = m->time_base; } // Some containers don't need metadata for some audio formats, // and for some formats extradata will be generated automatically, // set only what's needed int need_extradata = 0; switch (audio->config.out.codec & HB_ACODEC_MASK) { case HB_ACODEC_DCA: case HB_ACODEC_DCA_HD: track->st->codecpar->codec_id = AV_CODEC_ID_DTS; break; case HB_ACODEC_AC3: track->st->codecpar->codec_id = AV_CODEC_ID_AC3; break; case HB_ACODEC_FFEAC3: track->st->codecpar->codec_id = AV_CODEC_ID_EAC3; break; case HB_ACODEC_FFTRUEHD: track->st->codecpar->codec_id = AV_CODEC_ID_TRUEHD; break; case HB_ACODEC_MP2: track->st->codecpar->codec_id = AV_CODEC_ID_MP2; break; case HB_ACODEC_LAME: case HB_ACODEC_MP3: track->st->codecpar->codec_id = AV_CODEC_ID_MP3; break; case HB_ACODEC_VORBIS: track->st->codecpar->codec_id = AV_CODEC_ID_VORBIS; need_extradata = 1; break; case HB_ACODEC_OPUS: track->st->codecpar->codec_id = AV_CODEC_ID_OPUS; need_extradata = 1; break; case HB_ACODEC_FFALAC: case HB_ACODEC_FFALAC24: track->st->codecpar->codec_id = AV_CODEC_ID_ALAC; need_extradata = 1; break; case HB_ACODEC_FFFLAC: case HB_ACODEC_FFFLAC24: track->st->codecpar->codec_id = AV_CODEC_ID_FLAC; need_extradata = 1; break; case HB_ACODEC_FFAAC: case HB_ACODEC_CA_AAC: case HB_ACODEC_CA_HAAC: case HB_ACODEC_FDK_AAC: case HB_ACODEC_FDK_HAAC: track->st->codecpar->codec_id = AV_CODEC_ID_AAC; need_extradata = 1; // AAC from pass-through source may be ADTS. // Therefore inserting "aac_adtstoasc" bitstream filter is // preferred. // The filter does nothing for non-ADTS bitstream. if (audio->config.out.codec == HB_ACODEC_AAC_PASS) { const AVBitStreamFilter *bsf; AVBSFContext *ctx; int ret; bsf = av_bsf_get_by_name("aac_adtstoasc"); ret = av_bsf_alloc(bsf, &ctx); if (ret < 0) { hb_error("AAC bitstream filter: alloc failure"); goto error; } ctx->time_base_in.num = 1; ctx->time_base_in.den = audio->config.out.samplerate; track->bitstream_context = ctx; } break; default: hb_error("muxavformat: Unknown audio codec: %x", audio->config.out.codec); goto error; } if (need_extradata) { if (set_extradata(audio->priv.extradata, &track->st->codecpar->extradata, &track->st->codecpar->extradata_size)) { goto error; } } if (track->bitstream_context != NULL) { int ret; avcodec_parameters_copy(track->bitstream_context->par_in, track->st->codecpar); ret = av_bsf_init(track->bitstream_context); if (ret < 0) { hb_error("bitstream filter: init failure"); goto error; } } if (default_track_flag) { track->st->disposition |= AV_DISPOSITION_DEFAULT; default_track_flag = 0; } lang = lookup_lang_code(job->mux, audio->config.lang.iso639_2); if (lang != NULL) { av_dict_set(&track->st->metadata, "language", lang, 0); } track->st->codecpar->sample_rate = audio->config.out.samplerate; if (audio->config.out.codec & HB_ACODEC_PASS_FLAG) { av_channel_layout_copy(&track->st->codecpar->ch_layout, audio->config.in.ch_layout); } else { AVChannelLayout ch_layout = {0}; av_channel_layout_from_mask( &ch_layout, hb_ff_mixdown_xlat(audio->config.out.mixdown, NULL)); track->st->codecpar->ch_layout = ch_layout; } // Set audio track title const char *name = audio->config.out.name; if (name != NULL && name[0] != 0) { av_dict_set(&track->st->metadata, "title", name, 0); if (job->mux == HB_MUX_AV_MP4) { // Some software (MPC, mediainfo) use hdlr description // for track title av_dict_set(&track->st->metadata, "handler_name", name, 0); } } } // Check for audio track associations for (ii = 0; ii < hb_list_count(job->list_audio); ii++) { audio = hb_list_item(job->list_audio, ii); switch (audio->config.out.codec & HB_ACODEC_MASK) { case HB_ACODEC_FFAAC: case HB_ACODEC_CA_AAC: case HB_ACODEC_CA_HAAC: case HB_ACODEC_FDK_AAC: case HB_ACODEC_FDK_HAAC: break; default: { // Mark associated fallback audio tracks for any non-aac track for (jj = 0; jj < hb_list_count(job->list_audio); jj++) { hb_audio_t *fallback; int codec; if (ii == jj) continue; fallback = hb_list_item(job->list_audio, jj); codec = fallback->config.out.codec & HB_ACODEC_MASK; if (fallback->config.index == audio->config.index && (codec == HB_ACODEC_FFAAC || codec == HB_ACODEC_CA_AAC || codec == HB_ACODEC_CA_HAAC || codec == HB_ACODEC_FDK_AAC || codec == HB_ACODEC_FDK_HAAC)) { hb_mux_data_t *fallback_track; AVPacketSideData *sd; track = audio->priv.mux_data; fallback_track = fallback->priv.mux_data; sd = av_packet_side_data_new(&track->st->codecpar->coded_side_data, &track->st->codecpar->nb_coded_side_data, AV_PKT_DATA_FALLBACK_TRACK, sizeof(int), 0); if (sd != NULL) { int *data = (int *)sd->data; *data = fallback_track->st->index; } } } } break; } } int subtitle_default = -1; for (ii = 0; ii < hb_list_count(job->list_subtitle); ii++) { hb_subtitle_t *subtitle = hb_list_item(job->list_subtitle, ii); if (subtitle->config.dest == PASSTHRUSUB) { if (subtitle->config.default_track) subtitle_default = ii; } } // Quicktime requires that at least one subtitle is enabled, // else it doesn't show any of the subtitles. // So check to see if any of the subtitles are flagged to be // the default. The default will be the enabled track, else // enable the first track. if (job->mux == HB_MUX_AV_MP4 && subtitle_default == -1) { subtitle_default = 0; } for (ii = 0; ii < hb_list_count(job->list_subtitle); ii++) { hb_subtitle_t *subtitle; AVFormatContext *oc; const char *subtitle_muxer_name = NULL; subtitle = hb_list_item(job->list_subtitle, ii); if (subtitle->config.dest != PASSTHRUSUB) continue; track = m->tracks[m->ntracks++] = calloc(1, sizeof(hb_mux_data_t)); subtitle->mux_data = track; if (subtitle->config.external_filename != NULL) { subtitle_muxer_name = get_subtitle_muxer(subtitle->source); if (subtitle_muxer_name == NULL) { hb_error("No muxer for subtitle source %d", subtitle->source); goto error; } ret = avformat_alloc_output_context2(&track->oc, NULL, subtitle_muxer_name, subtitle->config.external_filename); if (ret < 0) { hb_error("Could not initialize subtitle avformat context."); goto error; } oc = track->oc; ret = avio_open2(&oc->pb, subtitle->config.external_filename, AVIO_FLAG_WRITE, &track->oc->interrupt_callback, NULL); if (ret < 0) { hb_error("subtitle avio_open2 failed, errno %d", ret); goto error; } } else { oc = m->oc; } track->type = MUX_TYPE_SUBTITLE; track->st = avformat_new_stream(oc, NULL); if (track->st == NULL) { hb_error("Could not initialize subtitle stream"); goto error; } track->st->codecpar->codec_type = AVMEDIA_TYPE_SUBTITLE; track->st->time_base = m->time_base; track->st->codecpar->width = subtitle->width; track->st->codecpar->height = subtitle->height; int need_extradata = 0; switch (subtitle->source) { case VOBSUB: { track->st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE; need_extradata = 1; } break; case PGSSUB: { track->st->codecpar->codec_id = AV_CODEC_ID_HDMV_PGS_SUBTITLE; } break; case DVBSUB: { track->st->codecpar->codec_id = AV_CODEC_ID_DVB_SUBTITLE; need_extradata = 1; } break; case CC608SUB: case CC708SUB: case SSASUB: case IMPORTSSA: { if (job->mux == HB_MUX_AV_MP4 && subtitle->config.external_filename == NULL) { track->st->codecpar->codec_id = AV_CODEC_ID_MOV_TEXT; } else { track->st->codecpar->codec_id = AV_CODEC_ID_ASS; need_fonts = 1; } need_extradata = 1; } break; case TX3GSUB: case UTF8SUB: case IMPORTSRT: { if (job->mux == HB_MUX_AV_MP4 && subtitle->config.external_filename == NULL) { track->st->codecpar->codec_id = AV_CODEC_ID_MOV_TEXT; } else { track->st->codecpar->codec_id = AV_CODEC_ID_SUBRIP; } need_extradata = 1; } break; default: continue; } if (need_extradata) { if (set_extradata(subtitle->extradata, &track->st->codecpar->extradata, &track->st->codecpar->extradata_size)) { goto error; } } if (ii == subtitle_default) { track->st->disposition |= AV_DISPOSITION_DEFAULT; } if (subtitle->config.default_track) { track->st->disposition |= AV_DISPOSITION_FORCED; } lang = lookup_lang_code(job->mux, subtitle->iso639_2); if (lang != NULL) { av_dict_set(&track->st->metadata, "language", lang, 0); } if (subtitle->config.name != NULL && subtitle->config.name[0] != 0) { // Set subtitle track title av_dict_set(&track->st->metadata, "title", subtitle->config.name, 0); if (job->mux == HB_MUX_AV_MP4) { // Some software (MPC, mediainfo) use hdlr description // for track title av_dict_set(&track->st->metadata, "handler_name", subtitle->config.name, 0); } } } if (need_fonts) { hb_list_t *list_attachment = job->list_attachment; int i; for (i = 0; i < hb_list_count(list_attachment); i++) { hb_attachment_t *attachment = hb_list_item(list_attachment, i); if ((attachment->type == FONT_TTF_ATTACH || attachment->type == FONT_OTF_ATTACH) && attachment->size > 0) { AVStream *st = avformat_new_stream(m->oc, NULL); if (st == NULL) { hb_error("Could not initialize attachment stream"); goto error; } st->codecpar->codec_type = AVMEDIA_TYPE_ATTACHMENT; if (attachment->type == FONT_TTF_ATTACH) { st->codecpar->codec_id = AV_CODEC_ID_TTF; } else if (attachment->type == FONT_OTF_ATTACH) { st->codecpar->codec_id = MKBETAG(0, 'O', 'T', 'F'); av_dict_set(&st->metadata, "mimetype", "application/vnd.ms-opentype", 0); } size_t priv_size = attachment->size; uint8_t *priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("Font extradata: malloc failure"); goto error; } memcpy(priv_data, attachment->data, priv_size); st->codecpar->extradata = priv_data; st->codecpar->extradata_size = priv_size; av_dict_set(&st->metadata, "filename", attachment->name, 0); } } } // Enable bitexact to avoid having // libavf putting an "Encoded by" metadata if (job->mux == HB_MUX_AV_MP4) { m->oc->flags |= AVFMT_FLAG_BITEXACT; } if (job->metadata) { hb_deep_log(2, "Writing Metadata to output file..."); if (job->metadata->dict) { hb_dict_iter_t iter = hb_dict_iter_init(job->metadata->dict); while (iter != HB_DICT_ITER_DONE) { const char *key; hb_value_t *val; hb_dict_iter_next_ex(job->metadata->dict, &iter, &key, &val); if (key != NULL && val != NULL) { const char *str = hb_value_get_string(val); if (str != NULL) { const char *mux_key = lookup_meta_mux_key(meta_mux, key); if (mux_key != NULL) { av_dict_set(&m->oc->metadata, mux_key, str, 0); } } } } if (job->mux == HB_MUX_AV_MP4) { // Set the location tag language to undefined, // Apple software seems to require it hb_value_t *location = hb_dict_get(job->metadata->dict, "Location"); if (location) { char *str = hb_value_get_string_xform(location); av_dict_set(&m->oc->metadata, "location-und", str, 0); free(str); } } } if (job->metadata->list_coverart) { hb_list_t *list_coverart = job->metadata->list_coverart; for (int ii = 0; ii < hb_list_count(list_coverart); ii++) { hb_coverart_t *art = hb_list_item(list_coverart, ii); enum AVCodecID codec_id = AV_CODEC_ID_NONE; const char *mimetype; const char *filename; switch (art->type) { case HB_ART_PNG: codec_id = AV_CODEC_ID_PNG; mimetype = "image/png"; filename = art->name ? art->name : "cover.png"; break; case HB_ART_JPEG: codec_id = AV_CODEC_ID_MJPEG; mimetype = "image/jpeg"; filename = art->name ? art->name : "cover.jpg"; break; default: break; } if (codec_id != AV_CODEC_ID_NONE) { AVStream *st = avformat_new_stream(m->oc, NULL); if (st == NULL) { hb_error("Could not initialize cover art stream"); goto error; } if (job->mux == HB_MUX_AV_MP4) { st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO; st->codecpar->codec_id = codec_id; st->codecpar->width = 640; st->codecpar->height = 360; st->disposition = AV_DISPOSITION_ATTACHED_PIC; } else { st->codecpar->codec_type = AVMEDIA_TYPE_ATTACHMENT; st->codecpar->codec_id = codec_id; av_dict_set(&st->metadata, "mimetype", mimetype, 0); av_dict_set(&st->metadata, "filename", filename, 0); size_t priv_size = art->size; uint8_t *priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("Cover art extradata: malloc failure"); goto error; } memcpy(priv_data, art->data, priv_size); st->codecpar->extradata = priv_data; st->codecpar->extradata_size = priv_size; } } } } } if (av_dict_get(m->oc->metadata, "creation_time", NULL, 0) == NULL) { time_t now = time(NULL); struct tm *now_utc = gmtime(&now); char now_8601[24]; strftime(now_8601, sizeof(now_8601), "%Y-%m-%dT%H:%M:%SZ", now_utc); av_dict_set(&m->oc->metadata, "creation_time", now_8601, 0); } char tool_string[80]; snprintf(tool_string, sizeof(tool_string), "HandBrake %s %i", HB_PROJECT_VERSION, HB_PROJECT_BUILD); av_dict_set(&m->oc->metadata, "encoding_tool", tool_string, 0); ret = avformat_write_header(m->oc, &av_opts); if (ret < 0) { hb_error("muxavformat: avformat_write_header failed!"); goto error; } AVDictionaryEntry *t = NULL; while ((t = av_dict_get(av_opts, "", t, AV_DICT_IGNORE_SUFFIX))) { hb_log("muxavformat: Unknown option %s", t->key); } av_dict_free(&av_opts); for (ii = 0; ii < m->ntracks; ii++) { if (m->tracks[ii]->oc != NULL) { ret = avformat_write_header(m->tracks[ii]->oc, NULL); if (ret < 0) { hb_error("muxavformat: avformat_write_header external track failed!"); goto error; } } } return 0; error: if (m->tracks) { for (ii = 0; ii < m->ntracks; ii++) { if (m->tracks[ii] != NULL && m->tracks[ii]->oc != NULL) { avformat_free_context(m->tracks[ii]->oc); } } } av_dict_free(&av_opts); free(job->mux_data); job->mux_data = NULL; avformat_free_context(m->oc); *job->done_error = HB_ERROR_INIT; *job->die = 1; return -1; } static int add_chapter(hb_mux_object_t *m, int64_t start, int64_t end, char *title) { AVChapter *chap; AVChapter **chapters; int nchap = m->oc->nb_chapters; nchap++; chapters = av_realloc(m->oc->chapters, nchap * sizeof(AVChapter *)); if (chapters == NULL) { hb_error("chapter array: malloc failure"); return -1; } chap = av_mallocz(sizeof(AVChapter)); if (chap == NULL) { hb_error("chapter: malloc failure"); return -1; } m->oc->chapters = chapters; m->oc->chapters[nchap - 1] = chap; m->oc->nb_chapters = nchap; chap->id = nchap; chap->time_base = m->tracks[0]->st->time_base; // libav does not currently have a good way to deal with chapters and // delayed stream timestamps. It makes no corrections to the chapter // track. A patch to libav would touch a lot of things, so for now, // work around the issue here. chap->start = start; chap->end = end; av_dict_set(&chap->metadata, "title", title, 0); return 0; } static int avformatMux(hb_mux_object_t *m, hb_mux_data_t *track, hb_buffer_t *buf) { int64_t dts, pts, duration = AV_NOPTS_VALUE; hb_job_t *job = m->job; uint8_t *sub_out = NULL; AVFormatContext *oc; oc = track->oc != NULL ? track->oc : m->oc; if (track->type == MUX_TYPE_VIDEO && (job->mux & HB_MUX_MASK_MP4)) { // compute dts duration for MP4 files hb_buffer_t *tmp; // delay by one frame so that we can compute duration properly. tmp = track->delay_buf; track->delay_buf = buf; buf = tmp; } if (buf == NULL) { if (job->mux == HB_MUX_AV_MP4 && track->oc == NULL && track->type == MUX_TYPE_SUBTITLE) { // Write a final "empty" subtitle to terminate the last // subtitle that was written if (track->duration > 0) { uint8_t empty[2] = {0, 0}; m->empty_pkt->data = empty; m->empty_pkt->size = 2; m->empty_pkt->dts = track->duration; m->empty_pkt->pts = track->duration; m->empty_pkt->duration = 90; m->empty_pkt->stream_index = track->st->index; av_interleaved_write_frame(m->oc, m->empty_pkt); av_packet_unref(m->empty_pkt); } } return 0; } if (track->type == MUX_TYPE_VIDEO && (job->mux & (HB_MUX_MASK_MKV | HB_MUX_MASK_WEBM)) && buf->s.renderOffset < 0) { // libav matroska muxer doesn't write dts to the output, but // if it sees a negative dts, it applies an offset to both pts // and dts to make it positive. This offset breaks chapter // start times and A/V sync. libav also requires that dts is // "monotonically increasing", which means it last_dts <= next_dts. // It also uses dts to determine track interleaving, so we need // to provide some reasonable dts value. // So when renderOffset < 0, set to 0 for mkv. buf->s.renderOffset = 0; // Note: for MP4, libav allows negative dts and creates an edts // (edit list) entry in this case. } if (buf->s.renderOffset == AV_NOPTS_VALUE) { dts = av_rescale_q(buf->s.start, (AVRational){1, 90000}, track->st->time_base); } else { dts = av_rescale_q(buf->s.renderOffset, (AVRational){1, 90000}, track->st->time_base); } pts = av_rescale_q(buf->s.start, (AVRational){1, 90000}, track->st->time_base); if (track->type == MUX_TYPE_VIDEO && track->delay_buf != NULL) { int64_t delayed_dts; delayed_dts = av_rescale_q(track->delay_buf->s.renderOffset, (AVRational){1, 90000}, track->st->time_base); duration = delayed_dts - dts; } if (duration < 0 && buf->s.duration > 0) { duration = av_rescale_q(buf->s.duration, (AVRational){1, 90000}, track->st->time_base); } if (duration < 0) { // There is a possibility that some subtitles get through the pipeline // without ever discovering their true duration. Make the duration // 10 seconds in this case. Unless they are PGS subs which should // have zero duration. if (track->type == MUX_TYPE_SUBTITLE && track->st->codecpar->codec_id != AV_CODEC_ID_HDMV_PGS_SUBTITLE) { duration = av_rescale_q(10, (AVRational){1, 1}, track->st->time_base); } else if (track->type == MUX_TYPE_VIDEO) { duration = av_rescale_q((int64_t)job->vrate.den * 90000 / job->vrate.num, (AVRational){1, 90000}, track->st->time_base); } else { duration = 0; } } m->pkt->data = buf->data; m->pkt->size = buf->size; m->pkt->dts = dts; m->pkt->pts = pts; m->pkt->duration = duration; if (track->type == MUX_TYPE_VIDEO) { if ((buf->s.frametype == HB_FRAME_IDR) || (buf->s.flags & HB_FLAG_FRAMETYPE_KEY)) { m->pkt->flags |= AV_PKT_FLAG_KEY; } if (!(buf->s.flags & HB_FLAG_FRAMETYPE_REF)) { m->pkt->flags |= AV_PKT_FLAG_DISPOSABLE; } } else if (buf->s.frametype & HB_FRAME_MASK_KEY) { m->pkt->flags |= AV_PKT_FLAG_KEY; } switch (track->type) { case MUX_TYPE_VIDEO: { if (job->chapter_markers && buf->s.new_chap) { if (track->current_chapter > 0) { hb_chapter_t *chapter; // reached chapter N, write marker for chapter N-1 // we don't know the end time of chapter N-1 till we receive // chapter N. So we are always writing the previous chapter // mark. // chapter numbers start at 1, but the list starts at 0 chapter = hb_list_item(job->list_chapter, track->current_chapter - 1); // make sure we're not writing a chapter that has 0 length if (chapter != NULL && track->prev_chapter_tc != AV_NOPTS_VALUE && track->prev_chapter_tc < m->pkt->pts) { char title[1024]; if (chapter->title != NULL) { snprintf(title, 1023, "%s", chapter->title); } else { snprintf(title, 1023, "Chapter %d", track->current_chapter); } add_chapter(m, track->prev_chapter_tc, m->pkt->pts, title); } } track->current_chapter = buf->s.new_chap; track->prev_chapter_tc = m->pkt->pts; } } break; case MUX_TYPE_SUBTITLE: { if (job->mux == HB_MUX_AV_MP4 && track->oc == NULL) { /* Write an empty sample */ if (track->duration < pts) { uint8_t empty[2] = {0, 0}; m->empty_pkt->data = empty; m->empty_pkt->size = 2; m->empty_pkt->dts = track->duration; m->empty_pkt->pts = track->duration; m->empty_pkt->duration = pts - track->duration; m->empty_pkt->stream_index = track->st->index; int ret = av_interleaved_write_frame(m->oc, m->empty_pkt); av_packet_unref(m->empty_pkt); if (ret < 0) { char errstr[64]; av_strerror(ret, errstr, sizeof(errstr)); hb_error("avformatMux: track %d, av_interleaved_write_frame failed " "with error '%s' (empty_pkt)", track->st->index, errstr); *job->done_error = HB_ERROR_UNKNOWN; *job->die = 1; return -1; } } } if (m->pkt->data == NULL) { // Memory allocation failure! hb_error("avformatMux: subtitle memory allocation failure"); *job->done_error = HB_ERROR_UNKNOWN; *job->die = 1; return -1; } } break; case MUX_TYPE_AUDIO: default: break; } track->duration = pts + m->pkt->duration; if (track->bitstream_context) { int ret; ret = av_bsf_send_packet(track->bitstream_context, m->pkt); if (ret < 0) { hb_error("avformatMux: track %d av_bsf_send_packet failed", track->st->index); return ret; } ret = av_bsf_receive_packet(track->bitstream_context, m->pkt); if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { return 0; } else if (ret < 0) { hb_error("avformatMux: track %d av_bsf_receive_packet failed", track->st->index); return ret; } } m->pkt->stream_index = track->st->index; int ret = av_interleaved_write_frame(oc, m->pkt); av_packet_unref(m->pkt); if (sub_out != NULL) { free(sub_out); } // Many avformat muxer functions do not check the error status // of the AVIOContext. So we need to check it ourselves to detect // write errors (like disk full condition). if (ret < 0 || oc->pb->error != 0) { char errstr[64]; av_strerror(ret < 0 ? ret : oc->pb->error, errstr, sizeof(errstr)); hb_error("avformatMux: track %d, av_interleaved_write_frame failed with " "error '%s'", track->st->index, errstr); *job->done_error = HB_ERROR_UNKNOWN; *job->die = 1; return -1; } hb_buffer_close(&buf); return 0; } static int avformatEnd(hb_mux_object_t *m) { hb_job_t *job = m->job; hb_mux_data_t *track = job->mux_data; if (!job->mux_data) { /* * We must have failed to create the file in the first place. */ return 0; } // Flush any delayed frames int ii; for (ii = 0; ii < m->ntracks; ii++) { avformatMux(m, m->tracks[ii], NULL); if (m->tracks[ii]->bitstream_context) { av_bsf_free(&m->tracks[ii]->bitstream_context); } } if (job->chapter_markers) { hb_chapter_t *chapter; // get the last chapter chapter = hb_list_item(job->list_chapter, track->current_chapter - 1); // only write the last chapter marker if it lasts at least 1.5 second if (chapter != NULL && chapter->duration > 135000LL) { char title[1024]; if (chapter->title != NULL) { snprintf(title, 1023, "%s", chapter->title); } else { snprintf(title, 1023, "Chapter %d", track->current_chapter); } add_chapter(m, track->prev_chapter_tc, track->duration, title); } } // Update and track private data that can change during // encode. for (ii = 0; ii < hb_list_count(job->list_audio); ii++) { AVStream *st; hb_audio_t *audio; audio = hb_list_item(job->list_audio, ii); st = audio->priv.mux_data->st; switch (audio->config.out.codec & HB_ACODEC_MASK) { case HB_ACODEC_FFFLAC: case HB_ACODEC_FFFLAC24: set_extradata(audio->priv.extradata, &st->codecpar->extradata, &st->codecpar->extradata_size); break; default: break; } } // Write MP4 cover art if (job->mux == HB_MUX_AV_MP4 && job->metadata) { hb_list_t *list_coverart = job->metadata->list_coverart; for (int ii = 0; ii < hb_list_count(list_coverart); ii++) { hb_coverart_t *art = hb_list_item(list_coverart, ii); m->pkt->data = art->data; m->pkt->size = art->size; m->pkt->stream_index = m->ntracks + ii; av_interleaved_write_frame(m->oc, m->pkt); av_packet_unref(m->pkt); } } av_write_trailer(m->oc); avio_close(m->oc->pb); avformat_free_context(m->oc); av_packet_free(&m->pkt); av_packet_free(&m->empty_pkt); m->oc = NULL; for (ii = 0; ii < m->ntracks; ii++) { if (m->tracks[ii]->oc != NULL) { av_write_trailer(m->tracks[ii]->oc); avio_close(m->tracks[ii]->oc->pb); avformat_free_context(m->tracks[ii]->oc); m->tracks[ii]->oc = NULL; } } for (int i = 0; i < m->ntracks; i++) { hb_mux_data_t *track = m->tracks[i]; m->tracks[i] = NULL; free(track); } free(m->tracks); return 0; } hb_mux_object_t *hb_mux_avformat_init(hb_job_t *job) { hb_mux_object_t *m = calloc(sizeof(hb_mux_object_t), 1); m->init = avformatInit; m->mux = avformatMux; m->end = avformatEnd; m->job = job; return m; }