Skip to content

Commit e82d104

Browse files
committed
video: Add support for playing F4V (MP4) files
1 parent f1d3594 commit e82d104

File tree

3 files changed

+230
-3
lines changed

3 files changed

+230
-3
lines changed

Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/Cargo.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ ttf-parser = "0.25"
6666
num-bigint = "0.4"
6767
unic-segment = "0.9.0"
6868
id3 = "1.16.1"
69+
# Parent commit of https://github.com/mozilla/mp4parse-rust/commit/9c646c20de89cc73df40f2eb0d5e7d728073e847,
70+
# (which is a downgrade of a dependency), so that we don't pull in a third copy of hashbrown (0.13).
71+
mp4parse = { git = "https://github.com/mozilla/mp4parse-rust.git", rev = "bab65cd41874bacb64f99001360d2ead7118f124" }
6972
either = "1.15.0"
7073
chardetng = "0.1.17"
7174
tracy-client = { version = "0.17.6", optional = true, default-features = false }

core/src/streams.rs

Lines changed: 195 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ use ruffle_video::frame::EncodedFrame;
3333
use ruffle_video::VideoStreamHandle;
3434
use std::cmp::max;
3535
use std::io::{Seek, SeekFrom};
36+
use std::sync::Arc;
3637
use swf::{AudioCompression, SoundFormat, VideoCodec, VideoDeblocking};
3738
use thiserror::Error;
3839
use url::Url;
@@ -177,6 +178,14 @@ pub enum NetStreamType {
177178
/// frame IDs ourselves for various API related purposes.
178179
frame_id: u32,
179180
},
181+
F4v {
182+
context: Arc<mp4parse::MediaContext>,
183+
184+
/// The currently playing video track's stream instance.
185+
video_stream: Option<VideoStreamHandle>,
186+
187+
frame_id: Option<u32>,
188+
},
180189
}
181190

182191
#[derive(Clone, Debug, Collect)]
@@ -525,6 +534,10 @@ impl<'gc> NetStream<'gc> {
525534
.expect("FLV reader stream position") as usize;
526535
}
527536

537+
if matches!(write.stream_type, Some(NetStreamType::F4v { .. })) {
538+
todo!("Seeking in F4V streams");
539+
}
540+
528541
drop(write);
529542

530543
if let Some(AvmObject::Avm2(_)) = self.0.read().avm_object {
@@ -809,8 +822,9 @@ impl<'gc> NetStream<'gc> {
809822
return false;
810823
}
811824

812-
match buffer.get(0..3) {
813-
Some([0x46, 0x4C, 0x56]) => {
825+
match buffer.get(0..8) {
826+
// Only version 1 is valid.
827+
Some([b'F', b'L', b'V', 1, _, _, _, _]) => {
814828
let mut reader = FlvReader::from_parts(&buffer, write.offset);
815829
match FlvHeader::parse(&mut reader) {
816830
Ok(header) => {
@@ -827,11 +841,32 @@ impl<'gc> NetStream<'gc> {
827841
Err(e) => {
828842
//TODO: Fire an error event to AS & stop playing too
829843
tracing::error!("FLV header parsing failed: {}", e);
830-
write.preload_offset = 3;
844+
write.preload_offset = 8; // ???
831845
false
832846
}
833847
}
834848
}
849+
// Video File Format Specification Version 10, page 32
850+
// Flash Player expects a valid F4V file to begin with the one of the following top-level boxes:
851+
// - ftyp (see “ftyp box” on page 18)
852+
// - moov (see “moov box” on page 19)
853+
// - mdat (see “mdat box” on page 32)
854+
// And the first 4 bytes are the length of the first box.
855+
Some([_, _, _, _, b'f', b't', b'y', b'p'])
856+
| Some([_, _, _, _, b'm', b'o', b'o', b'v'])
857+
| Some([_, _, _, _, b'm', b'd', b'a', b't']) => {
858+
println!("F4V");
859+
860+
let mut cursor = slice.as_cursor();
861+
let context = mp4parse::read_mp4(&mut cursor).unwrap();
862+
println!("{:#?}", context);
863+
write.stream_type = Some(NetStreamType::F4v {
864+
context: Arc::new(context),
865+
video_stream: None,
866+
frame_id: None,
867+
});
868+
true
869+
}
835870
Some(magic) => {
836871
//Unrecognized signature
837872
//TODO: Fire an error event to AS & stop playing too
@@ -1234,6 +1269,163 @@ impl<'gc> NetStream<'gc> {
12341269
}
12351270
}
12361271

1272+
if let Some(NetStreamType::F4v {
1273+
context: media_context,
1274+
frame_id,
1275+
video_stream,
1276+
}) = &mut write.stream_type
1277+
{
1278+
println!("frame id: {:?}, end_time: {}", frame_id, max_time);
1279+
1280+
let should_do_frame;
1281+
let sample_id;
1282+
1283+
match frame_id {
1284+
Some(frame) => {
1285+
should_do_frame = max_time > *frame as f64 * 1000.0 / 30.0;
1286+
1287+
if should_do_frame {
1288+
*frame_id = Some(*frame + 1);
1289+
sample_id = frame_id.unwrap();
1290+
} else {
1291+
sample_id = 0;
1292+
}
1293+
}
1294+
None => {
1295+
should_do_frame = true;
1296+
sample_id = 0;
1297+
*frame_id = Some(0);
1298+
}
1299+
}
1300+
1301+
if should_do_frame {
1302+
let sample_sizes = &media_context
1303+
.tracks
1304+
.get(1)
1305+
.unwrap()
1306+
.stsz
1307+
.as_ref()
1308+
.unwrap()
1309+
.sample_sizes;
1310+
let chunk_runs = &media_context
1311+
.tracks
1312+
.get(1)
1313+
.unwrap()
1314+
.stsc
1315+
.as_ref()
1316+
.unwrap()
1317+
.samples;
1318+
1319+
let get_samples_in_chunk = |chunk: u32| -> u32 {
1320+
let mut result = 0;
1321+
for cr in chunk_runs {
1322+
if (cr.first_chunk - 1) > chunk {
1323+
break;
1324+
}
1325+
result = cr.samples_per_chunk;
1326+
}
1327+
result
1328+
};
1329+
1330+
let chunk_of_sample = |sample: u32| -> (u32, u32) {
1331+
let mut sample_accum = 0;
1332+
for chunk in 0.. {
1333+
let samples_in_chunk = get_samples_in_chunk(chunk);
1334+
if sample_accum + samples_in_chunk > sample {
1335+
return (chunk, sample_accum);
1336+
}
1337+
sample_accum += samples_in_chunk;
1338+
}
1339+
(0, 0)
1340+
};
1341+
1342+
let (chunk, first_sample_in_chunk) = chunk_of_sample(sample_id);
1343+
let chunk_offsets = &media_context
1344+
.tracks
1345+
.get(1)
1346+
.unwrap()
1347+
.stco
1348+
.as_ref()
1349+
.unwrap()
1350+
.offsets;
1351+
1352+
let mut offs = chunk_offsets[chunk as usize] as usize;
1353+
1354+
for sam in first_sample_in_chunk..sample_id {
1355+
offs += sample_sizes[sam as usize] as usize;
1356+
}
1357+
1358+
let siz = sample_sizes[sample_id as usize] as usize;
1359+
1360+
println!("offs: {}, siz: {}", offs, siz);
1361+
let s = buffer;
1362+
1363+
let encoded_frame = EncodedFrame {
1364+
codec: VideoCodec::H264,
1365+
data: s[offs..offs + siz].as_ref(),
1366+
frame_id: frame_id.unwrap(),
1367+
};
1368+
1369+
let video_handle: VideoStreamHandle = match video_stream {
1370+
Some(stream) => *stream,
1371+
None => {
1372+
match context.video.register_video_stream(
1373+
1,
1374+
(8, 8),
1375+
VideoCodec::H264,
1376+
VideoDeblocking::UseVideoPacketValue,
1377+
) {
1378+
Ok(new_handle) => {
1379+
*video_stream = Some(new_handle);
1380+
1381+
new_handle
1382+
}
1383+
Err(e) => {
1384+
tracing::error!(
1385+
"Got error when registring FLV video stream: {}",
1386+
e
1387+
);
1388+
return; //TODO: This originally breaks and halts tag processing
1389+
}
1390+
}
1391+
}
1392+
};
1393+
1394+
let mdct = media_context.clone();
1395+
let trk = mdct.tracks.get(1).unwrap();
1396+
let stsd = trk.stsd.as_ref().unwrap();
1397+
let descs = stsd.descriptions.get(0).unwrap();
1398+
1399+
match descs {
1400+
mp4parse::SampleEntry::Video(video) => match &video.codec_specific {
1401+
mp4parse::VideoCodecSpecific::AVCConfig(avcconf) => {
1402+
if *frame_id == Some(0) {
1403+
println!("preloading avc config");
1404+
let _ = context.video.configure_video_stream_decoder(
1405+
video_handle,
1406+
avcconf.as_slice(),
1407+
);
1408+
}
1409+
}
1410+
mp4parse::VideoCodecSpecific::VPxConfig(_) => todo!(),
1411+
mp4parse::VideoCodecSpecific::AV1Config(_) => todo!(),
1412+
mp4parse::VideoCodecSpecific::ESDSConfig(_) => todo!(),
1413+
mp4parse::VideoCodecSpecific::H263Config(_) => todo!(),
1414+
mp4parse::VideoCodecSpecific::HEVCConfig(_) => todo!(),
1415+
},
1416+
mp4parse::SampleEntry::Audio(_) => todo!(),
1417+
mp4parse::SampleEntry::Unknown => todo!(),
1418+
}
1419+
1420+
write.last_decoded_bitmap = Some(
1421+
context
1422+
.video
1423+
.decode_video_stream_frame(video_handle, encoded_frame, context.renderer)
1424+
.unwrap(),
1425+
);
1426+
}
1427+
}
1428+
12371429
write.stream_time = max_time;
12381430
if let Err(e) = self.commit_sound_stream(context, &mut write) {
12391431
//TODO: Fire an error event at AS.

0 commit comments

Comments
 (0)