/* muxavformat.c Copyright (c) 2003-2019 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 #include "libavformat/avformat.h" #include "libavutil/avstring.h" #include "libavutil/intreadwrite.h" #include "hb.h" #include "ssautil.h" #include "lang.h" struct hb_mux_data_s { enum { MUX_TYPE_VIDEO, MUX_TYPE_AUDIO, MUX_TYPE_SUBTITLE } type; AVStream *st; int64_t duration; hb_buffer_t * delay_buf; int64_t prev_chapter_tc; int16_t current_chapter; AVBSFContext * bitstream_context; hb_tx3g_style_context_t * tx3g; }; struct hb_mux_object_s { HB_MUX_COMMON; hb_job_t * job; AVFormatContext * oc; AVRational time_base; int ntracks; hb_mux_data_t ** tracks; }; enum { META_TITLE, META_ARTIST, META_DIRECTOR, META_COMPOSER, META_RELEASE_DATE, META_COMMENT, META_ALBUM, META_GENRE, META_DESCRIPTION, META_SYNOPSIS, META_LAST }; enum { META_MUX_MP4, META_MUX_MKV, META_MUX_WEBM, META_MUX_LAST }; const char *metadata_keys[META_LAST][META_MUX_LAST] = { {"title", "TITLE"}, {"artist", "ARTIST"}, {"album_artist", "DIRECTOR"}, {"composer", "COMPOSER"}, {"date", "DATE_RELEASED"}, {"comment", "SUMMARY"}, {"album", NULL}, {"genre", "GENRE"}, {"description", "DESCRIPTION"}, {"synopsis", "SYNOPSIS"} }; static char* lookup_lang_code(int mux, char *iso639_2) { iso639_lang_t *lang; char *out = NULL; switch (mux) { case HB_MUX_AV_MP4: 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_2; break; default: break; } return out; } /********************************************************************** * 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; 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*)); 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); if (job->mp4_optimize) av_dict_set(&av_opts, "movflags", "faststart+disable_chpl", 0); else av_dict_set(&av_opts, "movflags", "+disable_chpl", 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; 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; 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 ) { 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; track->st->time_base = m->time_base; uint8_t *priv_data = NULL; int priv_size = 0; switch (job->vcodec) { case HB_VCODEC_X264_8BIT: case HB_VCODEC_X264_10BIT: case HB_VCODEC_QSV_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'); } /* Taken from x264 muxers.c */ priv_size = 5 + 1 + 2 + job->config.h264.sps_length + 1 + 2 + job->config.h264.pps_length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("H.264 extradata: malloc failure"); goto error; } priv_data[0] = 1; priv_data[1] = job->config.h264.sps[1]; /* AVCProfileIndication */ priv_data[2] = job->config.h264.sps[2]; /* profile_compat */ priv_data[3] = job->config.h264.sps[3]; /* AVCLevelIndication */ priv_data[4] = 0xff; // nalu size length is four bytes priv_data[5] = 0xe1; // one sps priv_data[6] = job->config.h264.sps_length >> 8; priv_data[7] = job->config.h264.sps_length; memcpy(priv_data+8, job->config.h264.sps, job->config.h264.sps_length); priv_data[8+job->config.h264.sps_length] = 1; // one pps priv_data[9+job->config.h264.sps_length] = job->config.h264.pps_length >> 8; priv_data[10+job->config.h264.sps_length] = job->config.h264.pps_length; memcpy(priv_data+11+job->config.h264.sps_length, job->config.h264.pps, job->config.h264.pps_length ); break; case HB_VCODEC_FFMPEG_VCE_H264: case HB_VCODEC_FFMPEG_NVENC_H264: case HB_VCODEC_FFMPEG_VT_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'); } if (job->config.extradata.length > 0) { priv_size = job->config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("H.264 extradata: malloc failure"); goto error; } memcpy(priv_data, job->config.extradata.bytes, job->config.extradata.length); } break; case HB_VCODEC_FFMPEG_MPEG4: track->st->codecpar->codec_id = AV_CODEC_ID_MPEG4; if (job->config.extradata.length > 0) { priv_size = job->config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("MPEG-4 extradata: malloc failure"); goto error; } memcpy(priv_data, job->config.extradata.bytes, job->config.extradata.length); } break; case HB_VCODEC_FFMPEG_MPEG2: track->st->codecpar->codec_id = AV_CODEC_ID_MPEG2VIDEO; if (job->config.extradata.length > 0) { priv_size = job->config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("MPEG-2 extradata: malloc failure"); goto error; } memcpy(priv_data, job->config.extradata.bytes, job->config.extradata.length); } break; case HB_VCODEC_FFMPEG_VP8: track->st->codecpar->codec_id = AV_CODEC_ID_VP8; priv_data = NULL; priv_size = 0; break; case HB_VCODEC_FFMPEG_VP9: track->st->codecpar->codec_id = AV_CODEC_ID_VP9; priv_data = NULL; priv_size = 0; break; case HB_VCODEC_THEORA: { track->st->codecpar->codec_id = AV_CODEC_ID_THEORA; int size = 0; ogg_packet *ogg_headers[3]; for (ii = 0; ii < 3; ii++) { ogg_headers[ii] = (ogg_packet *)job->config.theora.headers[ii]; size += ogg_headers[ii]->bytes + 2; } priv_size = size; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("Theora extradata: malloc failure"); goto error; } size = 0; for(ii = 0; ii < 3; ii++) { AV_WB16(priv_data + size, ogg_headers[ii]->bytes); size += 2; memcpy(priv_data+size, ogg_headers[ii]->packet, ogg_headers[ii]->bytes); size += ogg_headers[ii]->bytes; } } break; case HB_VCODEC_X265_8BIT: case HB_VCODEC_X265_10BIT: case HB_VCODEC_X265_12BIT: case HB_VCODEC_X265_16BIT: case HB_VCODEC_QSV_H265: case HB_VCODEC_QSV_H265_10BIT: 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'); } if (job->config.h265.headers_length > 0) { priv_size = job->config.h265.headers_length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("H.265 extradata: malloc failure"); goto error; } memcpy(priv_data, job->config.h265.headers, priv_size); } break; case HB_VCODEC_FFMPEG_VCE_H265: case HB_VCODEC_FFMPEG_NVENC_H265: case HB_VCODEC_FFMPEG_VT_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'); } if (job->config.extradata.length > 0) { priv_size = job->config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("H.265 extradata: malloc failure"); goto error; } memcpy(priv_data, job->config.extradata.bytes, job->config.extradata.length); } break; default: hb_error("muxavformat: Unknown video codec: %x", job->vcodec); goto error; } track->st->codecpar->extradata = priv_data; track->st->codecpar->extradata_size = priv_size; 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->disposition |= AV_DISPOSITION_DEFAULT; hb_rational_t vrate = job->vrate; // If the vrate is the internal clock rate, there's a good chance // this is 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" and make an adjustment // to fps.den. if (vrate.num == clock) { const hb_rate_t *video_framerate = NULL; while ((video_framerate = hb_video_framerate_get_next(video_framerate)) != NULL) { if (abs(vrate.den - video_framerate->rate) < 10) { vrate.den = video_framerate->rate; break; } } } hb_reduce(&vrate.num, &vrate.den, vrate.num, vrate.den); 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.config.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; } priv_data = NULL; priv_size = 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_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; int jj, size = 0; ogg_packet *ogg_headers[3]; for (jj = 0; jj < 3; jj++) { ogg_headers[jj] = (ogg_packet *)audio->priv.config.vorbis.headers[jj]; size += ogg_headers[jj]->bytes + 2; } priv_size = size; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("Vorbis extradata: malloc failure"); goto error; } size = 0; for(jj = 0; jj < 3; jj++) { AV_WB16(priv_data + size, ogg_headers[jj]->bytes); size += 2; memcpy(priv_data+size, ogg_headers[jj]->packet, ogg_headers[jj]->bytes); size += ogg_headers[jj]->bytes; } } break; case HB_ACODEC_OPUS: track->st->codecpar->codec_id = AV_CODEC_ID_OPUS; if (audio->priv.config.extradata.length) { priv_size = audio->priv.config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("OPUS extradata: malloc failure"); goto error; } memcpy(priv_data, audio->priv.config.extradata.bytes, audio->priv.config.extradata.length); } break; case HB_ACODEC_FFFLAC: case HB_ACODEC_FFFLAC24: track->st->codecpar->codec_id = AV_CODEC_ID_FLAC; if (audio->priv.config.extradata.length) { priv_size = audio->priv.config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("FLAC extradata: malloc failure"); goto error; } memcpy(priv_data, audio->priv.config.extradata.bytes, audio->priv.config.extradata.length); } 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; // libav mkv muxer expects there to be extradata for // AAC and will crash if it is NULL. // // Also, libav can over-read the buffer by up to 8 bytes // when it fills it's get_bits cache. // // So allocate extra bytes priv_size = audio->priv.config.extradata.length; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("AAC extradata: malloc failure"); goto error; } memcpy(priv_data, audio->priv.config.extradata.bytes, audio->priv.config.extradata.length); // 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 bistream 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; } track->st->codecpar->extradata = priv_data; track->st->codecpar->extradata_size = priv_size; 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("bistream 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) { track->st->codecpar->channels = av_get_channel_layout_nb_channels(audio->config.in.channel_layout); track->st->codecpar->channel_layout = audio->config.in.channel_layout; } else { track->st->codecpar->channels = hb_mixdown_get_discrete_channel_count(audio->config.out.mixdown); track->st->codecpar->channel_layout = hb_ff_mixdown_xlat(audio->config.out.mixdown, NULL); } const char *name; if (audio->config.out.name == NULL) { switch (track->st->codecpar->channels) { case 1: name = "Mono"; break; case 2: name = "Stereo"; break; default: name = "Surround"; break; } } else { name = audio->config.out.name; } // Set audio track title 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.in.track == audio->config.in.track && (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; int * sd; track = audio->priv.mux_data; fallback_track = fallback->priv.mux_data; sd = (int*)av_stream_new_side_data(track->st, AV_PKT_DATA_FALLBACK_TRACK, sizeof(int)); if (sd != NULL) { *sd = fallback_track->st->index; } } } } break; } } char * subidx_fmt = "size: %dx%d\n" "org: %d, %d\n" "scale: 100%%, 100%%\n" "alpha: 100%%\n" "smooth: OFF\n" "fadein/out: 50, 50\n" "align: OFF at LEFT TOP\n" "time offset: 0\n" "forced subs: %s\n" "palette: %06x, %06x, %06x, %06x, %06x, %06x, " "%06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x, %06x\n" "custom colors: OFF, tridx: 0000, " "colors: 000000, 000000, 000000, 000000\n"; 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; uint32_t rgb[16]; char subidx[2048]; int len; 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; track->type = MUX_TYPE_SUBTITLE; track->st = avformat_new_stream(m->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; priv_data = NULL; priv_size = 0; switch (subtitle->source) { case VOBSUB: { int jj; track->st->codecpar->codec_id = AV_CODEC_ID_DVD_SUBTITLE; for (jj = 0; jj < 16; jj++) rgb[jj] = hb_yuv2rgb(subtitle->palette[jj]); len = snprintf(subidx, 2048, subidx_fmt, subtitle->width, subtitle->height, 0, 0, "OFF", rgb[0], rgb[1], rgb[2], rgb[3], rgb[4], rgb[5], rgb[6], rgb[7], rgb[8], rgb[9], rgb[10], rgb[11], rgb[12], rgb[13], rgb[14], rgb[15]); priv_size = len + 1; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("VOBSUB extradata: malloc failure"); goto error; } memcpy(priv_data, subidx, priv_size); } break; case PGSSUB: { track->st->codecpar->codec_id = AV_CODEC_ID_HDMV_PGS_SUBTITLE; } break; case CC608SUB: case CC708SUB: case TX3GSUB: case UTF8SUB: case SSASUB: case IMPORTSRT: case IMPORTSSA: { if (job->mux == HB_MUX_AV_MP4) { track->st->codecpar->codec_id = AV_CODEC_ID_MOV_TEXT; track->tx3g = hb_tx3g_style_init( job->height, (char*)subtitle->extradata); } else { track->st->codecpar->codec_id = AV_CODEC_ID_ASS; need_fonts = 1; if (subtitle->extradata_size) { priv_size = subtitle->extradata_size; priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("SSA extradata: malloc failure"); goto error; } memcpy(priv_data, subtitle->extradata, priv_size); } } } break; default: continue; } if (track->st->codecpar->codec_id == AV_CODEC_ID_MOV_TEXT) { // Build codec extradata for tx3g. // If we were using a libav codec to generate this data // this would (or should) be done for us. uint8_t properties[] = { 0x00, 0x00, 0x00, 0x00, // Display Flags 0x01, // Horiz. Justification 0xff, // Vert. Justification 0x00, 0x00, 0x00, 0xff, // Bg color 0x00, 0x00, 0x00, 0x00, // Default text box 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Reserved 0x00, 0x01, // Font ID 0x00, // Font face 0x18, // Font size 0xff, 0xff, 0xff, 0xff, // Fg color // Font table: 0x00, 0x00, 0x00, 0x12, // Font table size 'f','t','a','b', // Tag 0x00, 0x01, // Count 0x00, 0x01, // Font ID 0x05, // Font name length 'A','r','i','a','l' // Font name }; int width, height, font_size; width = job->width * job->par.num / job->par.den; font_size = 0.05 * job->height; if (font_size < 12) { font_size = 12; } else if (font_size > 255) { font_size = 255; } properties[25] = font_size; height = 3 * font_size; track->st->codecpar->width = width; track->st->codecpar->height = height; properties[14] = height >> 8; properties[15] = height & 0xff; properties[16] = width >> 8; properties[17] = width & 0xff; priv_size = sizeof(properties); priv_data = av_malloc(priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { hb_error("TX3G extradata: malloc failure"); goto error; } memcpy(priv_data, properties, priv_size); } track->st->codecpar->extradata = priv_data; track->st->codecpar->extradata_size = priv_size; 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); } priv_size = attachment->size; 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); } } } if( job->metadata ) { hb_metadata_t *md = job->metadata; hb_deep_log(2, "Writing Metadata to output file..."); if (md->name && metadata_keys[META_TITLE][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_TITLE][meta_mux], md->name, 0); } if (md->artist && metadata_keys[META_ARTIST][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_ARTIST][meta_mux], md->artist, 0); } if (md->album_artist && metadata_keys[META_DIRECTOR][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_DIRECTOR][meta_mux], md->album_artist, 0); } if (md->composer && metadata_keys[META_COMPOSER][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_COMPOSER][meta_mux], md->composer, 0); } if (md->release_date && metadata_keys[META_RELEASE_DATE][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_RELEASE_DATE][meta_mux], md->release_date, 0); } if (md->comment && metadata_keys[META_COMMENT][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_COMMENT][meta_mux], md->comment, 0); } if (!md->name && md->album && metadata_keys[META_ALBUM][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_ALBUM][meta_mux], md->album, 0); } if (md->genre && metadata_keys[META_GENRE][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_GENRE][meta_mux], md->genre, 0); } if (md->description && metadata_keys[META_DESCRIPTION][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_DESCRIPTION][meta_mux], md->description, 0); } if (md->long_description && metadata_keys[META_SYNOPSIS][meta_mux] != NULL) { av_dict_set(&m->oc->metadata, metadata_keys[META_SYNOPSIS][meta_mux], md->long_description, 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); 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); ret = avformat_write_header(m->oc, &av_opts); if( ret < 0 ) { av_dict_free( &av_opts ); 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 ); return 0; error: 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->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) { AVPacket pkt; int64_t dts, pts, duration = AV_NOPTS_VALUE; hb_job_t * job = m->job; uint8_t * sub_out = NULL; 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->type == MUX_TYPE_SUBTITLE) { // Write a final "empty" subtitle to terminate the last // subtitle that was written if (track->duration > 0) { AVPacket empty_pkt; uint8_t empty[2] = {0,0}; av_init_packet(&empty_pkt); empty_pkt.data = empty; empty_pkt.size = 2; empty_pkt.dts = track->duration; empty_pkt.pts = track->duration; empty_pkt.duration = 90; empty_pkt.stream_index = track->st->index; av_interleaved_write_frame(m->oc, &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; } } av_init_packet(&pkt); pkt.data = buf->data; pkt.size = buf->size; pkt.dts = dts; pkt.pts = pts; pkt.duration = duration; if (track->type == MUX_TYPE_VIDEO) { if ((buf->s.frametype == HB_FRAME_IDR) || (buf->s.flags & HB_FLAG_FRAMETYPE_KEY)) { pkt.flags |= AV_PKT_FLAG_KEY; } #ifdef AV_PKT_FLAG_DISPOSABLE if (!(buf->s.flags & HB_FLAG_FRAMETYPE_REF)) { pkt.flags |= AV_PKT_FLAG_DISPOSABLE; } #endif } else if (buf->s.frametype & HB_FRAME_MASK_KEY) { 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 < 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, pkt.pts, title); } } track->current_chapter = buf->s.new_chap; track->prev_chapter_tc = pkt.pts; } } break; case MUX_TYPE_SUBTITLE: { if (job->mux == HB_MUX_AV_MP4) { /* Write an empty sample */ if ( track->duration < pts ) { AVPacket empty_pkt; uint8_t empty[2] = {0,0}; av_init_packet(&empty_pkt); empty_pkt.data = empty; empty_pkt.size = 2; empty_pkt.dts = track->duration; empty_pkt.pts = track->duration; empty_pkt.duration = pts - track->duration; empty_pkt.stream_index = track->st->index; int ret = av_interleaved_write_frame(m->oc, &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 (track->st->codecpar->codec_id == AV_CODEC_ID_MOV_TEXT) { uint8_t * styleatom; uint16_t stylesize = 0; uint8_t * buffer; uint16_t buffersize = 0; /* * Copy the subtitle into buffer stripping markup and * creating style atoms for them. */ hb_muxmp4_process_subtitle_style( track->tx3g, buf->data, &buffer, &styleatom, &stylesize); if (buffer != NULL) { buffersize = strlen((char*)buffer); if (styleatom == NULL) { stylesize = 0; } sub_out = malloc(2 + buffersize + stylesize); /* Write the subtitle sample */ memcpy(sub_out + 2, buffer, buffersize); memcpy(sub_out + 2 + buffersize, styleatom, stylesize); sub_out[0] = (buffersize >> 8) & 0xff; sub_out[1] = buffersize & 0xff; pkt.data = sub_out; pkt.size = buffersize + stylesize + 2; } free(buffer); free(styleatom); } } if (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 + pkt.duration; if (track->bitstream_context) { int ret; ret = av_bsf_send_packet(track->bitstream_context, &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, &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; } } pkt.stream_index = track->st->index; int ret = av_interleaved_write_frame(m->oc, &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 || m->oc->pb->error != 0) { char errstr[64]; av_strerror(ret < 0 ? ret : m->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 (m->tracks[ii]->tx3g) { hb_tx3g_style_close(&m->tracks[ii]->tx3g); } } 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: if( audio->priv.config.extradata.length ) { uint8_t *priv_data; int priv_size; priv_size = audio->priv.config.extradata.length; priv_data = av_realloc(st->codecpar->extradata, priv_size + AV_INPUT_BUFFER_PADDING_SIZE); if (priv_data == NULL) { break; } memcpy(priv_data, audio->priv.config.extradata.bytes, audio->priv.config.extradata.length); st->codecpar->extradata = priv_data; st->codecpar->extradata_size = priv_size; } break; default: break; } } av_write_trailer(m->oc); avio_close(m->oc->pb); avformat_free_context(m->oc); free(m->tracks); m->oc = NULL; 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; }