@@ -531,6 +531,11 @@ impl Mp4FileMuxer {
531531 /// 実際のデータ追記処理自体は利用側の責務であり、
532532 /// このメソッド目的は、その追記結果などを伝えることで、
533533 /// [`Mp4FileMuxer`] が適切に、MP4ファイルの再生に必要なメタデータを構築できるようにすることである。
534+ ///
535+ /// エラーを返した場合、内部状態 (`next_position` / `audio_chunks` / `video_chunks` /
536+ /// `last_sample_kind`) は変更されないため、呼び出し側は内容を補正したサンプルで再呼び出しできる。
537+ /// ただし、対象トラック種別の最初のサンプル投入直後に `MissingSampleEntry` エラーになった場合は、
538+ /// そのトラックの `audio_track_timescale` または `video_track_timescale` だけは記録済みとなる。
534539 pub fn append_sample ( & mut self , sample : & Sample ) -> Result < ( ) , MuxError > {
535540 if self . finalized_boxes . is_some ( ) {
536541 return Err ( MuxError :: AlreadyFinalized ) ;
@@ -545,7 +550,9 @@ impl Mp4FileMuxer {
545550 let metadata = SampleMetadata {
546551 duration : sample. duration ,
547552 keyframe : sample. keyframe ,
548- size : sample. data_size as u32 ,
553+ size : u32:: try_from ( sample. data_size ) . map_err ( |_| {
554+ MuxError :: EncodeError ( Error :: invalid_data ( "sample data size exceeds u32::MAX" ) )
555+ } ) ?,
549556 composition_time_offset : sample. composition_time_offset ,
550557 } ;
551558
@@ -973,19 +980,23 @@ impl Mp4FileMuxer {
973980 entries : chunks
974981 . iter ( )
975982 . enumerate ( )
976- . map ( |( i, c) | {
983+ . map ( |( i, c) | -> Result < StscEntry , MuxError > {
977984 let sample_description_index = sample_entries
978985 . iter ( )
979986 . position ( |entry| entry == & c. sample_entry )
980987 . map ( |idx| NonZeroU32 :: MIN . saturating_add ( idx as u32 ) )
981988 . expect ( "sample_entry should exist in sample_entries" ) ;
982- StscEntry {
989+ Ok ( StscEntry {
983990 first_chunk : NonZeroU32 :: MIN . saturating_add ( i as u32 ) ,
984- sample_per_chunk : c. samples . len ( ) as u32 ,
991+ sample_per_chunk : u32:: try_from ( c. samples . len ( ) ) . map_err ( |_| {
992+ MuxError :: EncodeError ( Error :: invalid_data (
993+ "samples per chunk exceeds u32::MAX" ,
994+ ) )
995+ } ) ?,
985996 sample_description_index,
986- }
997+ } )
987998 } )
988- . collect ( ) ,
999+ . collect :: < Result < Vec < _ > , _ > > ( ) ? ,
9891000 } ;
9901001
9911002 let stsz_box = StszBox :: Variable {
@@ -1123,6 +1134,8 @@ fn build_ctts_box(chunks: &[Chunk]) -> Result<Option<CttsBox>, MuxError> {
11231134#[ cfg( test) ]
11241135mod tests {
11251136 use super :: * ;
1137+ use alloc:: format;
1138+
11261139 use crate :: {
11271140 Uint ,
11281141 boxes:: {
@@ -1275,6 +1288,130 @@ mod tests {
12751288 ) ) ;
12761289 }
12771290
1291+ /// data_size が u32::MAX の境界値で append_sample と finalize が成功するテスト
1292+ ///
1293+ /// Mp4FileMuxer はメタデータのみを管理して実バイト列は確保しないため、
1294+ /// data_size に u32::MAX を渡しても 4 GiB の確保は発生しない。
1295+ /// next_position が u32::MAX を超えるため、finalize は Co64Box 経路を通る。
1296+ #[ test]
1297+ fn test_append_sample_data_size_u32_max_succeeds ( ) {
1298+ let mut muxer = Mp4FileMuxer :: new ( ) . expect ( "failed to create muxer" ) ;
1299+ let initial_size = muxer. initial_boxes_bytes ( ) . len ( ) as u64 ;
1300+
1301+ let sample = Sample {
1302+ track_kind : TrackKind :: Video ,
1303+ sample_entry : Some ( create_avc1_sample_entry ( ) ) ,
1304+ keyframe : true ,
1305+ timescale : NonZeroU32 :: MIN . saturating_add ( 30 - 1 ) ,
1306+ duration : 1 ,
1307+ composition_time_offset : None ,
1308+ data_offset : initial_size,
1309+ data_size : u32:: MAX as usize ,
1310+ } ;
1311+ muxer
1312+ . append_sample ( & sample)
1313+ . expect ( "failed to append sample with u32::MAX data_size" ) ;
1314+ let finalized = muxer. finalize ( ) . expect ( "failed to finalize muxer" ) ;
1315+ assert ! ( !finalized. moov_box_bytes. is_empty( ) ) ;
1316+ }
1317+
1318+ /// faststart 有効化 (with_options) 経路でも data_size の u32 境界が同様に防御されるテスト
1319+ #[ test]
1320+ fn test_append_sample_data_size_u32_max_with_faststart ( ) {
1321+ let options = Mp4FileMuxerOptions {
1322+ reserved_moov_box_size : 8192 ,
1323+ ..Default :: default ( )
1324+ } ;
1325+ let mut muxer =
1326+ Mp4FileMuxer :: with_options ( options) . expect ( "failed to create muxer with options" ) ;
1327+ let initial_size = muxer. initial_boxes_bytes ( ) . len ( ) as u64 ;
1328+
1329+ let sample = Sample {
1330+ track_kind : TrackKind :: Video ,
1331+ sample_entry : Some ( create_avc1_sample_entry ( ) ) ,
1332+ keyframe : true ,
1333+ timescale : NonZeroU32 :: MIN . saturating_add ( 30 - 1 ) ,
1334+ duration : 1 ,
1335+ composition_time_offset : None ,
1336+ data_offset : initial_size,
1337+ data_size : u32:: MAX as usize ,
1338+ } ;
1339+ muxer
1340+ . append_sample ( & sample)
1341+ . expect ( "failed to append sample with u32::MAX data_size under faststart" ) ;
1342+ let finalized = muxer. finalize ( ) . expect ( "failed to finalize muxer" ) ;
1343+ assert ! ( finalized. is_faststart_enabled( ) ) ;
1344+ }
1345+
1346+ /// data_size が u32::MAX を超える場合のエラーテスト
1347+ // 32-bit プラットフォームでは usize で u32::MAX + 1 を表現できず構造的に到達不能なため cfg で限定する
1348+ #[ test]
1349+ #[ cfg( target_pointer_width = "64" ) ]
1350+ fn test_append_sample_data_size_exceeds_u32_max ( ) {
1351+ let mut muxer = Mp4FileMuxer :: new ( ) . expect ( "failed to create muxer" ) ;
1352+ let initial_size = muxer. initial_boxes_bytes ( ) . len ( ) as u64 ;
1353+
1354+ let sample = Sample {
1355+ track_kind : TrackKind :: Video ,
1356+ sample_entry : Some ( create_avc1_sample_entry ( ) ) ,
1357+ keyframe : true ,
1358+ timescale : NonZeroU32 :: MIN . saturating_add ( 30 - 1 ) ,
1359+ duration : 1 ,
1360+ composition_time_offset : None ,
1361+ data_offset : initial_size,
1362+ data_size : u32:: MAX as usize + 1 ,
1363+ } ;
1364+ let err = muxer
1365+ . append_sample ( & sample)
1366+ . expect_err ( "expected encode error for data_size exceeding u32::MAX" ) ;
1367+ assert ! ( matches!( err, MuxError :: EncodeError ( _) ) ) ;
1368+ // MuxError::EncodeError は他原因でも返るためメッセージ内容まで確認する
1369+ let message = format ! ( "{err}" ) ;
1370+ assert ! (
1371+ message. contains( "sample data size exceeds u32::MAX" ) ,
1372+ "unexpected error message: {message}" ,
1373+ ) ;
1374+ }
1375+
1376+ /// append_sample がエラーを返した後にミューサ状態が変化していないことを検証するテスト
1377+ // 32-bit プラットフォームでは usize で u32::MAX + 1 を表現できず構造的に到達不能なため cfg で限定する
1378+ #[ test]
1379+ #[ cfg( target_pointer_width = "64" ) ]
1380+ fn test_append_sample_error_keeps_muxer_state ( ) {
1381+ let mut muxer = Mp4FileMuxer :: new ( ) . expect ( "failed to create muxer" ) ;
1382+ let initial_size = muxer. initial_boxes_bytes ( ) . len ( ) as u64 ;
1383+
1384+ let bad_sample = Sample {
1385+ track_kind : TrackKind :: Video ,
1386+ sample_entry : Some ( create_avc1_sample_entry ( ) ) ,
1387+ keyframe : true ,
1388+ timescale : NonZeroU32 :: MIN . saturating_add ( 30 - 1 ) ,
1389+ duration : 1 ,
1390+ composition_time_offset : None ,
1391+ data_offset : initial_size,
1392+ data_size : u32:: MAX as usize + 1 ,
1393+ } ;
1394+ muxer
1395+ . append_sample ( & bad_sample)
1396+ . expect_err ( "expected encode error for data_size exceeding u32::MAX" ) ;
1397+
1398+ // エラー後でも next_position は初期値のままなので、同じ data_offset で再投入できる
1399+ let good_sample = Sample {
1400+ track_kind : TrackKind :: Video ,
1401+ sample_entry : Some ( create_avc1_sample_entry ( ) ) ,
1402+ keyframe : true ,
1403+ timescale : NonZeroU32 :: MIN . saturating_add ( 30 - 1 ) ,
1404+ duration : 1 ,
1405+ composition_time_offset : None ,
1406+ data_offset : initial_size,
1407+ data_size : 1024 ,
1408+ } ;
1409+ muxer
1410+ . append_sample ( & good_sample)
1411+ . expect ( "failed to append sample after error" ) ;
1412+ muxer. finalize ( ) . expect ( "failed to finalize muxer" ) ;
1413+ }
1414+
12781415 /// 音声と映像の複数トラックのテスト
12791416 #[ test]
12801417 fn test_audio_and_video_tracks ( ) {
0 commit comments