static int vc1_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) { if( !importer->info ) return LSMASH_ERR_NAMELESS; if( track_number != 1 ) return LSMASH_ERR_FUNCTION_PARAM; vc1_importer_t *vc1_imp = (vc1_importer_t *)importer->info; vc1_info_t *info = &vc1_imp->info; importer_status current_status = importer->status; if( current_status == IMPORTER_ERROR ) return LSMASH_ERR_NAMELESS; if( current_status == IMPORTER_EOF ) return IMPORTER_EOF; int err = vc1_importer_get_access_unit_internal( importer, 0 ); if( err < 0 ) { importer->status = IMPORTER_ERROR; return err; } lsmash_sample_t *sample = lsmash_create_sample( vc1_imp->max_au_length ); if( !sample ) return LSMASH_ERR_MEMORY_ALLOC; *p_sample = sample; vc1_access_unit_t *access_unit = &info->access_unit; vc1_importer_check_eof( importer, access_unit ); sample->dts = vc1_imp->ts_list.timestamp[ access_unit->number - 1 ].dts; sample->cts = vc1_imp->ts_list.timestamp[ access_unit->number - 1 ].cts; sample->prop.leading = access_unit->independent || access_unit->non_bipredictive || sample->cts >= vc1_imp->last_ref_intra_cts ? ISOM_SAMPLE_IS_NOT_LEADING : ISOM_SAMPLE_IS_UNDECODABLE_LEADING; if( access_unit->independent && !access_unit->disposable ) vc1_imp->last_ref_intra_cts = sample->cts; if( vc1_imp->composition_reordering_present && !access_unit->disposable && !access_unit->closed_gop ) sample->prop.allow_earlier = QT_SAMPLE_EARLIER_PTS_ALLOWED; sample->prop.independent = access_unit->independent ? ISOM_SAMPLE_IS_INDEPENDENT : ISOM_SAMPLE_IS_NOT_INDEPENDENT; sample->prop.disposable = access_unit->disposable ? ISOM_SAMPLE_IS_DISPOSABLE : ISOM_SAMPLE_IS_NOT_DISPOSABLE; sample->prop.redundant = ISOM_SAMPLE_HAS_NO_REDUNDANCY; if( access_unit->random_accessible ) /* All random access point is a sync sample even if it's an open RAP. */ sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; sample->length = access_unit->data_length; memcpy( sample->data, access_unit->data, access_unit->data_length ); return current_status; }
static int wave_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) { if( !importer->info ) return LSMASH_ERR_NAMELESS; if( track_number != 1 ) return LSMASH_ERR_FUNCTION_PARAM; lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_get_entry_data( importer->summaries, track_number ); if( !summary ) return LSMASH_ERR_NAMELESS; wave_importer_t *wave_imp = (wave_importer_t *)importer->info; importer_status current_status = importer->status; if( current_status == IMPORTER_ERROR ) return LSMASH_ERR_NAMELESS; if( current_status == IMPORTER_EOF ) return IMPORTER_EOF; if( wave_imp->number_of_samples / summary->samples_in_frame > wave_imp->au_number ) wave_imp->au_length = summary->bytes_per_frame; else { wave_imp->au_length = wave_imp->fmt.wfx.nBlockAlign * (wave_imp->number_of_samples % summary->samples_in_frame); importer->status = IMPORTER_EOF; if( wave_imp->au_length == 0 ) return IMPORTER_EOF; } lsmash_sample_t *sample = lsmash_create_sample( wave_imp->au_length ); if( !sample ) return LSMASH_ERR_MEMORY_ALLOC; *p_sample = sample; if( lsmash_bs_get_bytes_ex( importer->bs, wave_imp->au_length, sample->data ) != wave_imp->au_length ) { importer->status = IMPORTER_ERROR; return LSMASH_ERR_INVALID_DATA; } sample->length = wave_imp->au_length; sample->dts = wave_imp->au_number ++ * summary->samples_in_frame; sample->cts = sample->dts; sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; return current_status; }
static int dts_importer_get_accessunit( importer_t *importer, uint32_t track_number, lsmash_sample_t **p_sample ) { if( !importer->info ) return LSMASH_ERR_NAMELESS; if( track_number != 1 ) return LSMASH_ERR_FUNCTION_PARAM; lsmash_audio_summary_t *summary = (lsmash_audio_summary_t *)lsmash_get_entry_data( importer->summaries, track_number ); if( !summary ) return LSMASH_ERR_NAMELESS; dts_importer_t *dts_imp = (dts_importer_t *)importer->info; dts_info_t *info = &dts_imp->info; importer_status current_status = importer->status; if( current_status == IMPORTER_ERROR ) return LSMASH_ERR_NAMELESS; if( current_status == IMPORTER_EOF && dts_imp->au_length == 0 ) return IMPORTER_EOF; if( current_status == IMPORTER_CHANGE ) summary->max_au_length = 0; lsmash_sample_t *sample = lsmash_create_sample( dts_imp->au_length ); if( !sample ) return LSMASH_ERR_MEMORY_ALLOC; *p_sample = sample; memcpy( sample->data, dts_imp->au, dts_imp->au_length ); sample->length = dts_imp->au_length; sample->dts = dts_imp->au_number++ * summary->samples_in_frame; sample->cts = sample->dts; sample->prop.ra_flags = ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC; sample->prop.pre_roll.distance = !!(info->flags & DTS_EXT_SUBSTREAM_LBR_FLAG); /* MDCT */ if( importer->status == IMPORTER_EOF ) { dts_imp->au_length = 0; return 0; } if( dts_importer_get_next_accessunit_internal( importer ) < 0 ) importer->status = IMPORTER_ERROR; return current_status; }
static int write_frame( hnd_t handle, uint8_t *p_nalu, int i_size, x264_picture_t *p_picture ) { mp4_hnd_t *p_mp4 = handle; uint64_t dts, cts; if( !p_mp4->i_numframe ) { p_mp4->i_start_offset = p_picture->i_dts * -1; p_mp4->i_first_cts = p_mp4->b_dts_compress ? 0 : p_mp4->i_start_offset * p_mp4->i_time_inc; if( p_mp4->b_fragments ) { lsmash_edit_t edit; edit.duration = ISOM_EDIT_DURATION_UNKNOWN32; /* QuickTime doesn't support 64bit duration. */ edit.start_time = p_mp4->i_first_cts; edit.rate = ISOM_EDIT_MODE_NORMAL; MP4_LOG_IF_ERR( lsmash_create_explicit_timeline_map( p_mp4->p_root, p_mp4->i_track, edit ), "failed to set timeline map for video.\n" ); } } lsmash_sample_t *p_sample = lsmash_create_sample( i_size + p_mp4->i_sei_size ); MP4_FAIL_IF_ERR( !p_sample, "failed to create a video sample data.\n" ); if( p_mp4->p_sei_buffer ) { memcpy( p_sample->data, p_mp4->p_sei_buffer, p_mp4->i_sei_size ); free( p_mp4->p_sei_buffer ); p_mp4->p_sei_buffer = NULL; } memcpy( p_sample->data + p_mp4->i_sei_size, p_nalu, i_size ); p_mp4->i_sei_size = 0; if( p_mp4->b_dts_compress ) { if( p_mp4->i_numframe == 1 ) p_mp4->i_init_delta = (p_picture->i_dts + p_mp4->i_start_offset) * p_mp4->i_time_inc; dts = p_mp4->i_numframe > p_mp4->i_delay_frames ? p_picture->i_dts * p_mp4->i_time_inc : p_mp4->i_numframe * (p_mp4->i_init_delta / p_mp4->i_dts_compress_multiplier); cts = p_picture->i_pts * p_mp4->i_time_inc; } else { dts = (p_picture->i_dts + p_mp4->i_start_offset) * p_mp4->i_time_inc; cts = (p_picture->i_pts + p_mp4->i_start_offset) * p_mp4->i_time_inc; } p_sample->dts = dts; p_sample->cts = cts; p_sample->index = p_mp4->i_sample_entry; p_sample->prop.ra_flags = p_picture->b_keyframe ? ISOM_SAMPLE_RANDOM_ACCESS_FLAG_SYNC : ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE; if( p_mp4->b_fragments && p_mp4->i_numframe && p_sample->prop.ra_flags != ISOM_SAMPLE_RANDOM_ACCESS_FLAG_NONE ) { MP4_FAIL_IF_ERR( lsmash_flush_pooled_samples( p_mp4->p_root, p_mp4->i_track, p_sample->dts - p_mp4->i_prev_dts ), "failed to flush the rest of samples.\n" ); MP4_FAIL_IF_ERR( lsmash_create_fragment_movie( p_mp4->p_root ), "failed to create a movie fragment.\n" ); } /* Append data per sample. */ MP4_FAIL_IF_ERR( lsmash_append_sample( p_mp4->p_root, p_mp4->i_track, p_sample ), "failed to append a video frame.\n" ); p_mp4->i_prev_dts = dts; p_mp4->i_numframe++; return i_size; }
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; }
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; }