static int write_headers( hnd_t handle, x264_nal_t *p_nal ) { mp4_hnd_t *p_mp4 = handle; uint32_t sps_size = p_nal[0].i_payload - H264_NALU_LENGTH_SIZE; uint32_t pps_size = p_nal[1].i_payload - H264_NALU_LENGTH_SIZE; uint32_t sei_size = p_nal[2].i_payload; uint8_t *sps = p_nal[0].p_payload + H264_NALU_LENGTH_SIZE; uint8_t *pps = p_nal[1].p_payload + H264_NALU_LENGTH_SIZE; uint8_t *sei = p_nal[2].p_payload; lsmash_codec_specific_t *cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); lsmash_h264_specific_parameters_t *param = (lsmash_h264_specific_parameters_t *)cs->data.structured; param->lengthSizeMinusOne = H264_NALU_LENGTH_SIZE - 1; /* SPS * The remaining parameters are automatically set by SPS. */ if( lsmash_append_h264_parameter_set( param, H264_PARAMETER_SET_TYPE_SPS, sps, sps_size ) ) { MP4_LOG_ERROR( "failed to append SPS.\n" ); return -1; } /* PPS */ if( lsmash_append_h264_parameter_set( param, H264_PARAMETER_SET_TYPE_PPS, pps, pps_size ) ) { MP4_LOG_ERROR( "failed to append PPS.\n" ); return -1; } if( lsmash_add_codec_specific_data( (lsmash_summary_t *)p_mp4->summary, cs ) ) { MP4_LOG_ERROR( "failed to add H.264 specific info.\n" ); return -1; } lsmash_destroy_codec_specific_data( cs ); /* Additional extensions */ /* Bitrate info */ cs = lsmash_create_codec_specific_data( LSMASH_CODEC_SPECIFIC_DATA_TYPE_ISOM_VIDEO_H264_BITRATE, LSMASH_CODEC_SPECIFIC_FORMAT_STRUCTURED ); if( cs ) lsmash_add_codec_specific_data( (lsmash_summary_t *)p_mp4->summary, cs ); lsmash_destroy_codec_specific_data( cs ); p_mp4->i_sample_entry = lsmash_add_sample_entry( p_mp4->p_root, p_mp4->i_track, p_mp4->summary ); MP4_FAIL_IF_ERR( !p_mp4->i_sample_entry, "failed to add sample entry for video.\n" ); /* SEI */ p_mp4->p_sei_buffer = malloc( sei_size ); MP4_FAIL_IF_ERR( !p_mp4->p_sei_buffer, "failed to allocate sei transition buffer.\n" ); memcpy( p_mp4->p_sei_buffer, sei, sei_size ); p_mp4->i_sei_size = sei_size; return sei_size + sps_size + pps_size; }
static int wave_importer_probe( importer_t *importer ) { wave_importer_t *wave_imp = create_wave_importer( importer ); if( !wave_imp ) return LSMASH_ERR_MEMORY_ALLOC; int err = 0; uint32_t filesize; lsmash_bs_t *bs = importer->bs; if( lsmash_bs_get_be32( bs ) != LSMASH_4CC( 'R', 'I', 'F', 'F' ) || ((filesize = lsmash_bs_get_le32( bs ) + 8) < WAVE_MIN_FILESIZE && filesize > 8) || lsmash_bs_get_be32( bs ) != LSMASH_4CC( 'W', 'A', 'V', 'E' ) ) { err = LSMASH_ERR_INVALID_DATA; goto fail; } int fmt_chunk_present = 0; int data_chunk_present = 0; while( !bs->eob && !(fmt_chunk_present && data_chunk_present) ) { uint32_t ckID = lsmash_bs_get_be32( bs ); uint32_t ckSize = lsmash_bs_get_le32( bs ); lsmash_bs_reset_counter( bs ); switch( ckID ) { case LSMASH_4CC( 'f', 'm', 't', ' ' ) : if( ckSize < 16 ) { err = LSMASH_ERR_INVALID_DATA; goto fail; } if( (err = wave_parse_fmt_chunk( wave_imp, bs )) < 0 ) goto fail; fmt_chunk_present = 1; break; case LSMASH_4CC( 'd', 'a', 't', 'a' ) : if( !fmt_chunk_present ) { /* The 'fmt ' chunk must be present before the 'data' chunk. */ err = LSMASH_ERR_INVALID_DATA; goto fail; } wave_imp->chunk.data_offset = lsmash_bs_get_stream_pos( bs ); wave_imp->chunk.length = ckSize; wave_imp->chunk.number = 1; wave_imp->chunk.file = importer->file; wave_imp->number_of_samples = ckSize / wave_imp->fmt.wfx.nBlockAlign; data_chunk_present = 1; break; default : break; } if( !data_chunk_present ) { /* Skip the rest of this chunk. * Note that ckData is word-aligned even if ckSize is an odd number. */ uint32_t skip_size = ckSize; if( skip_size & 1 ) skip_size++; if( skip_size > lsmash_bs_count( bs ) ) { skip_size -= lsmash_bs_count( bs ); lsmash_bs_read_seek( bs, skip_size, SEEK_CUR ); } } } if( !(fmt_chunk_present && data_chunk_present) ) { err = LSMASH_ERR_INVALID_DATA; goto fail; } /* Make fake movie. * Treat WAVE file format as if it's QuickTime file format. */ uint32_t track_ID; lsmash_movie_parameters_t movie_param = { 0 }; lsmash_track_parameters_t track_param = { 0 }; lsmash_media_parameters_t media_param = { 0 }; importer->file->qt_compatible = 1; if( (err = lsmash_importer_make_fake_movie( importer )) < 0 || (err = lsmash_importer_make_fake_track( importer, ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK, &track_ID )) < 0 || (err = lsmash_get_movie_parameters( importer->root, &movie_param )) < 0 || (err = lsmash_get_track_parameters( importer->root, track_ID, &track_param )) < 0 || (err = lsmash_get_media_parameters( importer->root, track_ID, &media_param )) < 0 ) goto fail; movie_param.timescale = wave_imp->fmt.wfx.nSamplesPerSec; media_param.timescale = wave_imp->fmt.wfx.nSamplesPerSec; if( (err = lsmash_set_movie_parameters( importer->root, &movie_param )) < 0 || (err = lsmash_set_track_parameters( importer->root, track_ID, &track_param )) < 0 || (err = lsmash_set_media_parameters( importer->root, track_ID, &media_param )) < 0 ) goto fail; lsmash_audio_summary_t *summary = wave_create_summary( &wave_imp->fmt ); if( !summary || lsmash_add_sample_entry( importer->root, track_ID, summary ) != 1 ) { lsmash_cleanup_summary( (lsmash_summary_t *)summary ); err = LSMASH_ERR_NAMELESS; goto fail; } if( (err = lsmash_add_entry( importer->summaries, summary )) < 0 ) { lsmash_cleanup_summary( (lsmash_summary_t *)summary ); goto fail; } importer->info = wave_imp; importer->status = IMPORTER_OK; return 0; fail: lsmash_importer_break_fake_movie( importer ); remove_wave_importer( wave_imp ); importer->file->qt_compatible = 0; importer->info = NULL; return err; }
int lsmash_create_reference_chapter_track( lsmash_root_t *root, uint32_t track_ID, char *file_name ) { if( isom_check_initializer_present( root ) < 0 ) goto error_message; lsmash_file_t *file = root->file; if( !file || !file->moov || !file->moov->mvhd ) goto error_message; if( file->forbid_tref || (!file->qt_compatible && !file->itunes_movie) ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "reference chapter is not available for this file.\n" ); goto error_message; } FILE *chapter = NULL; /* shut up 'uninitialized' warning */ /* Create a Track Reference Box. */ isom_trak_t *trak = isom_get_trak( file, track_ID ); if( !trak ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "the specified track ID to apply the chapter doesn't exist.\n" ); goto error_message; } if( !trak->tref && !isom_add_tref( trak ) ) goto error_message; /* Create a track_ID for a new chapter track. */ uint32_t *id = (uint32_t *)lsmash_malloc( sizeof(uint32_t) ); if( !id ) goto error_message; uint32_t chapter_track_ID = *id = file->moov->mvhd->next_track_ID; /* Create a Track Reference Type Box. */ isom_tref_type_t *chap = isom_add_track_reference_type( trak->tref, QT_TREF_TYPE_CHAP ); if( !chap ) { lsmash_free( id ); goto error_message; } chap->ref_count = 1; chap->track_ID = id; /* Create a reference chapter track. */ if( chapter_track_ID != lsmash_create_track( root, ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) ) goto error_message; /* Set track parameters. */ lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters( &track_param ); track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; if( lsmash_set_track_parameters( root, chapter_track_ID, &track_param ) < 0 ) goto fail; /* Set media parameters. */ uint64_t media_timescale = lsmash_get_media_timescale( root, track_ID ); if( !media_timescale ) goto fail; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); media_param.timescale = media_timescale; media_param.ISO_language = file->max_3gpp_version >= 6 || file->itunes_movie ? ISOM_LANGUAGE_CODE_UNDEFINED : 0; media_param.MAC_language = 0; if( lsmash_set_media_parameters( root, chapter_track_ID, &media_param ) < 0 ) goto fail; /* Create a sample description. */ lsmash_codec_type_t sample_type = file->max_3gpp_version >= 6 || file->itunes_movie ? ISOM_CODEC_TYPE_TX3G_TEXT : QT_CODEC_TYPE_TEXT_TEXT; lsmash_summary_t summary = { .sample_type = sample_type, .data_ref_index = 1 }; uint32_t sample_entry = lsmash_add_sample_entry( root, chapter_track_ID, &summary ); if( !sample_entry ) goto fail; /* Check each line format. */ fn_get_chapter_data fnc = isom_check_chap_line( file_name ); if( !fnc ) goto fail; /* Open chapter format file. */ chapter = lsmash_fopen( file_name, "rb" ); if( !chapter ) { lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); goto fail; } /* Parse the file and write text samples. */ isom_chapter_entry_t data; while( !fnc( chapter, &data ) ) { /* set start_time */ data.start_time = data.start_time * 1e-9 * media_timescale + 0.5; /* write a text sample here */ int is_qt_text = lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ); uint16_t name_length = strlen( data.chapter_name ); lsmash_sample_t *sample = lsmash_create_sample( 2 + name_length + 12 * is_qt_text ); if( !sample ) { lsmash_free( data.chapter_name ); goto fail; } sample->data[0] = (name_length >> 8) & 0xff; sample->data[1] = name_length & 0xff; memcpy( sample->data + 2, data.chapter_name, name_length ); if( is_qt_text ) { /* QuickTime Player requires Text Encoding Attribute Box ('encd') if media language is ISO language codes : undefined. * Also this box can avoid garbling if the QuickTime text sample is encoded by Unicode characters. * Note: 3GPP Timed Text supports only UTF-8 or UTF-16, so this box isn't needed. */ static const uint8_t encd[12] = { 0x00, 0x00, 0x00, 0x0C, /* size: 12 */ 0x65, 0x6E, 0x63, 0x64, /* type: 'encd' */ 0x00, 0x00, 0x01, 0x00 /* Unicode Encoding */ }; memcpy( sample->data + 2 + name_length, encd, 12 ); } sample->dts = data.start_time; sample->cts = data.start_time; sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; sample->index = sample_entry; if( lsmash_append_sample( root, chapter_track_ID, sample ) < 0 ) { lsmash_free( data.chapter_name ); goto fail; } lsmash_freep( &data.chapter_name ); } if( lsmash_flush_pooled_samples( root, chapter_track_ID, 0 ) < 0 ) goto fail; isom_trak_t *chapter_trak = isom_get_trak( file, chapter_track_ID ); if( !chapter_trak ) goto fail; fclose( chapter ); chapter_trak->is_chapter = 1; chapter_trak->related_track_ID = track_ID; return 0; fail: if( chapter ) fclose( chapter ); /* Remove chapter track reference. */ if( trak->tref->ref_list.tail ) isom_remove_box_by_itself( trak->tref->ref_list.tail->data ); if( trak->tref->ref_list.entry_count == 0 ) isom_remove_box_by_itself( trak->tref ); /* Remove the reference chapter track attached at tail of the list. */ if( file->moov->trak_list.tail ) isom_remove_box_by_itself( file->moov->trak_list.tail->data ); error_message: lsmash_log( NULL, LSMASH_LOG_ERROR, "failed to set reference chapter.\n" ); return LSMASH_ERR_NAMELESS; } uint32_t lsmash_count_tyrant_chapter( lsmash_root_t *root ) { if( isom_check_initializer_present( root ) < 0 && root->file->initializer->moov && root->file->initializer->moov->udta && root->file->initializer->moov->udta->chpl && root->file->initializer->moov->udta->chpl->list ) return root->file->initializer->moov->udta->chpl->list->entry_count; return 0; } char *lsmash_get_tyrant_chapter( lsmash_root_t *root, uint32_t index, double *timestamp ) { if( isom_check_initializer_present( root ) < 0 ) return NULL; lsmash_file_t *file = root->file->initializer; if( !file->moov || !file->moov->mvhd || !file->moov->udta || !file->moov->udta->chpl ) return NULL; isom_chpl_t *chpl = file->moov->udta->chpl; isom_chpl_entry_t *data = (isom_chpl_entry_t *)lsmash_get_entry_data( chpl->list, index ); if( !data ) return NULL; double timescale = chpl->version ? 10000000.0 : file->moov->mvhd->timescale; *timestamp = data->start_time / timescale; if( !memcmp( data->chapter_name, UTF8_BOM, UTF8_BOM_LENGTH ) ) return data->chapter_name + UTF8_BOM_LENGTH; return data->chapter_name; } int lsmash_print_chapter_list( lsmash_root_t *root ) { if( isom_check_initializer_present( root ) < 0 || !(root->file->initializer->flags & LSMASH_FILE_MODE_READ) ) return LSMASH_ERR_FUNCTION_PARAM; lsmash_file_t *file = root->file->initializer; if( file->moov && file->moov->udta && file->moov->udta->chpl ) { isom_chpl_t *chpl = file->moov->udta->chpl; uint32_t timescale; if( !chpl->version ) { if( !file->moov && !file->moov->mvhd ) return LSMASH_ERR_NAMELESS; timescale = file->moov->mvhd->timescale; } else timescale = 10000000; uint32_t i = 1; for( lsmash_entry_t *entry = chpl->list->head; entry; entry = entry->next ) { isom_chpl_entry_t *data = (isom_chpl_entry_t *)entry->data; int64_t start_time = data->start_time / timescale; int hh = start_time / 3600; int mm = (start_time / 60) % 60; int ss = start_time % 60; int ms = ((data->start_time / (double)timescale) - hh * 3600 - mm * 60 - ss) * 1e3 + 0.5; if( !memcmp( data->chapter_name, UTF8_BOM, UTF8_BOM_LENGTH ) ) /* detect BOM */ { data->chapter_name += UTF8_BOM_LENGTH; #ifdef _WIN32 if( i == 1 ) printf( UTF8_BOM ); /* add BOM on Windows */ #endif } printf( "CHAPTER%02"PRIu32"=%02d:%02d:%02d.%03d\n", i, hh, mm, ss, ms ); printf( "CHAPTER%02"PRIu32"NAME=%s\n", i++, data->chapter_name ); } return 0; } lsmash_log( NULL, LSMASH_LOG_ERROR, "this file doesn't have a chapter list.\n" ); return LSMASH_ERR_NAMELESS; }
bool fcMP4Muxer::mux(const Params ¶ms) { lsmash_root_t *root; lsmash_file_parameters_t mp4_stream; lsmash_file_parameters_t h264_stream; lsmash_brand_type major_brand = ISOM_BRAND_TYPE_MP42; lsmash_brand_type compatible_brands[2] = { ISOM_BRAND_TYPE_MP42, ISOM_BRAND_TYPE_ISOM }; uint32_t fps_num = params.frame_rate; uint32_t fps_den = 1; // 出力 mp4 root = lsmash_create_root(); if (lsmash_open_file(params.out_mp4_path, 0, &mp4_stream) != 0) { return false; } mp4_stream.major_brand = major_brand; mp4_stream.brands = compatible_brands; mp4_stream.brand_count = sizeof(compatible_brands) / sizeof(compatible_brands[0]); mp4_stream.minor_version = 0; lsmash_set_file(root, &mp4_stream); lsmash_movie_parameters_t movie_param; lsmash_initialize_movie_parameters(&movie_param); lsmash_set_movie_parameters(root, &movie_param); int track_number = 1; MP4TrackData track_data[2]; int num_track_data = 0; if (params.in_h264_path) { importer_t *h264_importer = lsmash_importer_open(params.in_h264_path, "H.264"); if (h264_importer == nullptr) { return false; } int h264_track_id = lsmash_create_track(root, ISOM_MEDIA_HANDLER_TYPE_VIDEO_TRACK); lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters(&track_param); (int&)track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; int sample_entry = 0; lsmash_summary_t *summary = lsmash_duplicate_summary(h264_importer, track_number); auto video_summary = (lsmash_video_summary_t*)summary; track_param.display_width = video_summary->width << 16; track_param.display_height = video_summary->height << 16; lsmash_set_track_parameters(root, h264_track_id, &track_param); sample_entry = lsmash_add_sample_entry(root, h264_track_id, summary); auto& td = track_data[num_track_data++]; td.importer = h264_importer; td.summary = summary; td.sample_entry = sample_entry; td.track_number = track_number; td.track_id = h264_track_id; td.timescale = video_summary->timescale; td.timebase = video_summary->timebase; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters(&media_param); media_param.timescale = td.timescale; lsmash_set_media_parameters(root, h264_track_id, &media_param); } if (params.in_aac_path) { importer_t *aac_importer = lsmash_importer_open(params.in_aac_path, "adts"); if (aac_importer == nullptr) { return false; } int aac_track_id = lsmash_create_track(root, ISOM_MEDIA_HANDLER_TYPE_AUDIO_TRACK); lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters(&track_param); (int&)track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; int sample_entry = 0; lsmash_summary_t *summary = lsmash_duplicate_summary(aac_importer, track_number); auto audio_summary = (lsmash_audio_summary_t*)summary; lsmash_set_track_parameters(root, aac_track_id, &track_param); sample_entry = lsmash_add_sample_entry(root, aac_track_id, summary); auto& td = track_data[num_track_data++]; td.importer = aac_importer; td.summary = summary; td.sample_entry = sample_entry; td.track_number = track_number; td.track_id = aac_track_id; td.timescale = audio_summary->frequency; td.timebase = 1; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters(&media_param); media_param.timescale = td.timescale; lsmash_set_media_parameters(root, aac_track_id, &media_param); } double largest_dts = 0.0; uint32_t num_consecutive_sample_skip = 0; for (int ti = 0;;) { auto &td = track_data[ti]; if (!td.sample) { int ret = lsmash_importer_get_access_unit(td.importer, td.track_number, &td.sample); if (ret <= -1) // error { lsmash_delete_sample(td.sample); break; } else if (ret == 1) /* a change of stream's properties */ { lsmash_cleanup_summary(td.summary); td.summary = lsmash_duplicate_summary(td.importer, td.track_number); td.sample_entry = lsmash_add_sample_entry(root, td.track_id, td.summary); if (!td.sample_entry) { break; } } else if (ret == 2) /* EOF */ { lsmash_delete_sample(td.sample); break; } if (td.sample) { td.sample->index = td.sample_entry; td.sample->dts *= td.timebase; td.sample->cts *= td.timebase; td.dts = (double)td.sample->dts / td.timescale; } } if (td.sample) { if (td.dts <= largest_dts || num_consecutive_sample_skip == num_track_data) { uint64_t sample_size = td.sample->length; uint64_t sample_dts = td.sample->dts; uint64_t sample_cts = td.sample->cts; if (lsmash_append_sample(root, td.track_id, td.sample)) { return false; } td.prev_dts = sample_dts; td.sample = nullptr; largest_dts = std::max<double>(largest_dts, td.dts); num_consecutive_sample_skip = 0; } else { // skip ++num_consecutive_sample_skip; } } ti = (ti + 1) % num_track_data; } for (int i = 0; i < num_track_data; ++i) { lsmash_flush_pooled_samples(root, track_data[i].track_id, fps_den); lsmash_cleanup_summary(track_data[i].summary); lsmash_importer_close(track_data[i].importer); } lsmash_finish_movie(root, nullptr); lsmash_close_file(&mp4_stream); lsmash_destroy_root(root); return true; }
int lsmash_create_reference_chapter_track( lsmash_root_t *root, uint32_t track_ID, char *file_name ) { if( !root || !root->moov || !root->moov->mvhd || !root->moov->trak_list ) goto error_message; if( !root->qt_compatible && !root->itunes_movie ) { lsmash_log( LSMASH_LOG_ERROR, "reference chapter is not available for this file.\n" ); goto error_message; } FILE *chapter = NULL; /* shut up 'uninitialized' warning */ /* Create a Track Reference Box. */ isom_trak_entry_t *trak = isom_get_trak( root, track_ID ); if( !trak ) { lsmash_log( LSMASH_LOG_ERROR, "the specified track ID to apply the chapter doesn't exist.\n" ); goto error_message; } if( isom_add_tref( trak ) ) goto error_message; /* Create a track_ID for a new chapter track. */ uint32_t *id = (uint32_t *)malloc( sizeof(uint32_t) ); if( !id ) goto error_message; uint32_t chapter_track_ID = *id = root->moov->mvhd->next_track_ID; /* Create a Track Reference Type Box. */ isom_tref_type_t *chap = isom_add_track_reference_type( trak->tref, QT_TREF_TYPE_CHAP, 1, id ); if( !chap ) goto error_message; /* no need to free id */ /* Create a reference chapter track. */ if( chapter_track_ID != lsmash_create_track( root, ISOM_MEDIA_HANDLER_TYPE_TEXT_TRACK ) ) goto error_message; /* Set track parameters. */ lsmash_track_parameters_t track_param; lsmash_initialize_track_parameters( &track_param ); track_param.mode = ISOM_TRACK_IN_MOVIE | ISOM_TRACK_IN_PREVIEW; if( lsmash_set_track_parameters( root, chapter_track_ID, &track_param ) ) goto fail; /* Set media parameters. */ uint64_t media_timescale = lsmash_get_media_timescale( root, track_ID ); if( !media_timescale ) goto fail; lsmash_media_parameters_t media_param; lsmash_initialize_media_parameters( &media_param ); media_param.timescale = media_timescale; media_param.ISO_language = root->max_3gpp_version >= 6 || root->itunes_movie ? ISOM_LANGUAGE_CODE_UNDEFINED : 0; media_param.MAC_language = 0; if( lsmash_set_media_parameters( root, chapter_track_ID, &media_param ) ) goto fail; /* Create a sample description. */ lsmash_codec_type_t sample_type = root->max_3gpp_version >= 6 || root->itunes_movie ? ISOM_CODEC_TYPE_TX3G_TEXT : QT_CODEC_TYPE_TEXT_TEXT; lsmash_summary_t summary = { .sample_type = sample_type }; uint32_t sample_entry = lsmash_add_sample_entry( root, chapter_track_ID, &summary ); if( !sample_entry ) goto fail; /* Check each line format. */ fn_get_chapter_data fnc = isom_check_chap_line( file_name ); if( !fnc ) goto fail; /* Open chapter format file. */ chapter = fopen( file_name, "rb" ); if( !chapter ) { lsmash_log( LSMASH_LOG_ERROR, "failed to open the chapter file \"%s\".\n", file_name ); goto fail; } /* Parse the file and write text samples. */ isom_chapter_entry_t data; while( !fnc( chapter, &data ) ) { /* set start_time */ data.start_time = data.start_time * 1e-9 * media_timescale + 0.5; /* write a text sample here */ int is_qt_text = lsmash_check_codec_type_identical( sample_type, QT_CODEC_TYPE_TEXT_TEXT ); uint16_t name_length = strlen( data.chapter_name ); lsmash_sample_t *sample = lsmash_create_sample( 2 + name_length + 12 * is_qt_text ); if( !sample ) { free( data.chapter_name ); goto fail; } sample->data[0] = (name_length >> 8) & 0xff; sample->data[1] = name_length & 0xff; memcpy( sample->data + 2, data.chapter_name, name_length ); if( is_qt_text ) { /* QuickTime Player requires Text Encoding Attribute Box ('encd') if media language is ISO language codes : undefined. * Also this box can avoid garbling if the QuickTime text sample is encoded by Unicode characters. * Note: 3GPP Timed Text supports only UTF-8 or UTF-16, so this box isn't needed. */ static const uint8_t encd[12] = { 0x00, 0x00, 0x00, 0x0C, /* size: 12 */ 0x65, 0x6E, 0x63, 0x64, /* type: 'encd' */ 0x00, 0x00, 0x01, 0x00 /* Unicode Encoding */ }; memcpy( sample->data + 2 + name_length, encd, 12 ); } sample->dts = sample->cts = data.start_time; sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; sample->index = sample_entry; if( lsmash_append_sample( root, chapter_track_ID, sample ) ) { free( data.chapter_name ); goto fail; } free( data.chapter_name ); data.chapter_name = NULL; } if( lsmash_flush_pooled_samples( root, chapter_track_ID, 0 ) ) goto fail; isom_trak_entry_t *chapter_trak = isom_get_trak( root, chapter_track_ID ); if( !chapter_trak ) goto fail; fclose( chapter ); chapter_trak->is_chapter = 1; chapter_trak->related_track_ID = track_ID; return 0; fail: if( chapter ) fclose( chapter ); /* Remove chapter track reference. */ lsmash_remove_entry_direct( trak->tref->ref_list, trak->tref->ref_list->tail, isom_remove_track_reference_type ); if( trak->tref->ref_list->entry_count == 0 ) isom_remove_tref( trak->tref ); /* Remove the reference chapter track attached at tail of the list. */ lsmash_remove_entry_direct( root->moov->trak_list, root->moov->trak_list->tail, isom_remove_trak ); error_message: lsmash_log( LSMASH_LOG_ERROR, "failed to set reference chapter.\n" ); return -1; }