diff --git a/src/read.rs b/src/read.rs index 2756fbda9..b8d0877c7 100644 --- a/src/read.rs +++ b/src/read.rs @@ -391,6 +391,15 @@ impl Reader { Tag::SetBackgroundColor(try!(tag_reader.read_rgb())) }, + Some(TagCode::SoundStreamHead) => Tag::SoundStreamHead( + // TODO: Disallow certain compressions. + Box::new(try!(tag_reader.read_sound_stream_info())) + ), + + Some(TagCode::SoundStreamHead2) => Tag::SoundStreamHead2( + Box::new(try!(tag_reader.read_sound_stream_info())) + ), + Some(TagCode::StartSound) => Tag::StartSound { id: try!(tag_reader.read_u16()), sound_info: Box::new(try!(tag_reader.read_sound_info())), @@ -621,42 +630,35 @@ impl Reader { fn read_define_sound(&mut self) -> Result { let id = try!(self.read_u16()); - let flags = try!(self.read_u8()); - let compression = match flags >> 4 { - 0 => AudioCompression::UncompressedUnknownEndian, - 1 => AudioCompression::Adpcm, - 2 => AudioCompression::Mp3, - 3 => AudioCompression::Uncompressed, - 4 => AudioCompression::Nellymoser16Khz, - 5 => AudioCompression::Nellymoser8Khz, - 6 => AudioCompression::Nellymoser, - 11 => AudioCompression::Speex, - _ => return Err(Error::new(ErrorKind::InvalidData, - "Invalid audio format.")), - }; - let sample_rate = match (flags & 0b11_00) >> 2 { - 0 => 5512, - 1 => 11025, - 2 => 22050, - 3 => 44100, - _ => unreachable!(), - }; - let is_16_bit = (flags & 0b10) != 0; - let is_stereo = (flags & 0b1) != 0; + let format = try!(self.read_sound_format()); let num_samples = try!(self.read_u32()); let mut data = Vec::new(); try!(self.input.read_to_end(&mut data)); Ok(Tag::DefineSound(Box::new(Sound { id: id, - compression: compression, - sample_rate: sample_rate, - is_16_bit: is_16_bit, - is_stereo: is_stereo, + format: format, num_samples: num_samples, data: data }))) } + fn read_sound_stream_info(&mut self) -> Result { + // TODO: Verify version requirements. + let playback_format = try!(self.read_sound_format()); + let stream_format = try!(self.read_sound_format()); + let num_samples_per_block = try!(self.read_u16()); + let latency_seek = if stream_format.compression == AudioCompression::Mp3 { + // Specs say this is i16, not u16. How are negative values used? + try!(self.read_i16()) + } else { 0 }; + Ok(SoundStreamInfo { + stream_format: stream_format, + playback_format: playback_format, + num_samples_per_block: num_samples_per_block, + latency_seek: latency_seek, + }) + } + fn read_shape_styles(&mut self, shape_version: u8) -> Result { let num_fill_styles = match try!(self.read_u8()) { 0xff if shape_version >= 2 => try!(self.read_u16()) as usize, @@ -1228,6 +1230,37 @@ impl Reader { Ok(filter) } + fn read_sound_format(&mut self) -> Result { + let flags = try!(self.read_u8()); + let compression = match flags >> 4 { + 0 => AudioCompression::UncompressedUnknownEndian, + 1 => AudioCompression::Adpcm, + 2 => AudioCompression::Mp3, + 3 => AudioCompression::Uncompressed, + 4 => AudioCompression::Nellymoser16Khz, + 5 => AudioCompression::Nellymoser8Khz, + 6 => AudioCompression::Nellymoser, + 11 => AudioCompression::Speex, + _ => return Err(Error::new(ErrorKind::InvalidData, + "Invalid audio format.")), + }; + let sample_rate = match (flags & 0b11_00) >> 2 { + 0 => 5512, + 1 => 11025, + 2 => 22050, + 3 => 44100, + _ => unreachable!(), + }; + let is_16_bit = (flags & 0b10) != 0; + let is_stereo = (flags & 0b1) != 0; + Ok(SoundFormat { + compression: compression, + sample_rate: sample_rate, + is_16_bit: is_16_bit, + is_stereo: is_stereo, + }) + } + fn read_sound_info(&mut self) -> Result { let flags = try!(self.read_u8()); let event = match (flags >> 4) & 0b11 { diff --git a/src/test_data.rs b/src/test_data.rs index a9708ecb1..69fab856d 100644 --- a/src/test_data.rs +++ b/src/test_data.rs @@ -272,10 +272,12 @@ pub fn tag_tests() -> Vec { vec![ 4, Tag::DefineSound(Box::new(Sound { id: 1, - compression: AudioCompression::Uncompressed, - sample_rate: 44100, - is_16_bit: true, - is_stereo: false, + format: SoundFormat { + compression: AudioCompression::Uncompressed, + sample_rate: 44100, + is_16_bit: true, + is_stereo: false, + }, num_samples: 10, data: vec![255, 127, 0, 128, 255, 127, 0, 128, 255, 127, 0, 128, 255, 127, 0, 128, 255, 127, 0, 128], })), @@ -574,6 +576,27 @@ pub fn tag_tests() -> Vec { vec![ (1, Tag::ShowFrame, vec![0b01_000000, 0]), + ( + 3, + Tag::SoundStreamHead2(Box::new(SoundStreamInfo { + stream_format: SoundFormat { + compression: AudioCompression::Uncompressed, + sample_rate: 5512, + is_16_bit: true, + is_stereo: false, + }, + playback_format: SoundFormat { + compression: AudioCompression::UncompressedUnknownEndian, + sample_rate: 5512, + is_16_bit: true, + is_stereo: false, + }, + num_samples_per_block: 229, + latency_seek: 0, + })), + read_tag_bytes_from_file("tests/swfs/soundstreamhead2.swf", TagCode::SoundStreamHead2) + ), + ( 9, Tag::SymbolClass(vec![ diff --git a/src/types.rs b/src/types.rs index 6716fcfe8..f03c9fa14 100644 --- a/src/types.rs +++ b/src/types.rs @@ -291,6 +291,8 @@ pub enum Tag { ImportAssets { url: String, imports: Vec }, SetBackgroundColor(Color), SetTabIndex { depth: Depth, tab_index: u16 }, + SoundStreamHead(Box), + SoundStreamHead2(Box), StartSound { id: CharacterId, sound_info: Box }, StartSound2 { class_name: String, sound_info: Box }, SymbolClass(Vec), @@ -336,10 +338,7 @@ pub struct Shape { #[derive(Debug,PartialEq,Clone)] pub struct Sound { pub id: CharacterId, - pub compression: AudioCompression, - pub sample_rate: u16, - pub is_16_bit: bool, - pub is_stereo: bool, + pub format: SoundFormat, pub num_samples: u32, pub data: Vec, } @@ -506,4 +505,20 @@ pub enum AudioCompression { Nellymoser8Khz, Nellymoser, Speex, +} + +#[derive(Debug,PartialEq,Clone)] +pub struct SoundFormat { + pub compression: AudioCompression, + pub sample_rate: u16, + pub is_stereo: bool, + pub is_16_bit: bool, +} + +#[derive(Debug,PartialEq,Clone)] +pub struct SoundStreamInfo { + pub stream_format: SoundFormat, + pub playback_format: SoundFormat, + pub num_samples_per_block: u16, + pub latency_seek: i16, } \ No newline at end of file diff --git a/src/write.rs b/src/write.rs index 0bad3b075..342ae85dd 100644 --- a/src/write.rs +++ b/src/write.rs @@ -454,6 +454,14 @@ impl Writer { try!(self.write_i16(depth)); }, + &Tag::SoundStreamHead(ref sound_stream_info) => { + try!(self.write_sound_stream_head(sound_stream_info, 1)); + } + + &Tag::SoundStreamHead2(ref sound_stream_info) => { + try!(self.write_sound_stream_head(sound_stream_info, 2)); + } + &Tag::StartSound { id, ref sound_info } => { let length = 3 + if let Some(_) = sound_info.in_sample { 4 } else { 0 } @@ -599,25 +607,7 @@ impl Writer { 7 + sound.data.len() as u32 )); try!(self.write_u16(sound.id)); - try!(self.write_ubits(4, match sound.compression { - AudioCompression::UncompressedUnknownEndian => 0, - AudioCompression::Adpcm => 1, - AudioCompression::Mp3 => 2, - AudioCompression::Uncompressed => 3, - AudioCompression::Nellymoser16Khz => 4, - AudioCompression::Nellymoser8Khz => 5, - AudioCompression::Nellymoser => 6, - AudioCompression::Speex => 11, - })); - try!(self.write_ubits(2, match sound.sample_rate { - 5512 => 0, - 11025 => 1, - 22050 => 2, - 44100 => 3, - _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid sample rate.")), - })); - try!(self.write_bit(sound.is_16_bit)); - try!(self.write_bit(sound.is_stereo)); + try!(self.write_sound_format(&sound.format)); try!(self.write_u32(sound.num_samples)); try!(self.output.write_all(&sound.data)); Ok(()) @@ -1186,6 +1176,52 @@ impl Writer { Ok(()) } + fn write_sound_stream_head(&mut self, stream_info: &SoundStreamInfo, version: u8) -> Result<()> { + let tag_code = if version >= 2 { + TagCode::SoundStreamHead2 + } else { + TagCode::SoundStreamHead + }; + // MP3 compression has added latency seek field. + let length = if stream_info.stream_format.compression == AudioCompression::Mp3 { + 6 + } else { + 4 + }; + try!(self.write_tag_header(tag_code, length)); + try!(self.write_sound_format(&stream_info.playback_format)); + try!(self.write_sound_format(&stream_info.stream_format)); + try!(self.write_u16(stream_info.num_samples_per_block)); + if stream_info.stream_format.compression == AudioCompression::Mp3 { + try!(self.write_i16(stream_info.latency_seek)); + } + Ok(()) + } + + fn write_sound_format(&mut self, sound_format: &SoundFormat) -> Result<()> { + try!(self.write_ubits(4, match sound_format.compression { + AudioCompression::UncompressedUnknownEndian => 0, + AudioCompression::Adpcm => 1, + AudioCompression::Mp3 => 2, + AudioCompression::Uncompressed => 3, + AudioCompression::Nellymoser16Khz => 4, + AudioCompression::Nellymoser8Khz => 5, + AudioCompression::Nellymoser => 6, + AudioCompression::Speex => 11, + })); + try!(self.write_ubits(2, match sound_format.sample_rate { + 5512 => 0, + 11025 => 1, + 22050 => 2, + 44100 => 3, + _ => return Err(Error::new(ErrorKind::InvalidData, "Invalid sample rate.")), + })); + try!(self.write_bit(sound_format.is_16_bit)); + try!(self.write_bit(sound_format.is_stereo)); + try!(self.flush_bits()); + Ok(()) + } + fn write_sound_info(&mut self, sound_info: &SoundInfo) -> Result<()> { let flags = match sound_info.event { diff --git a/tests/swfs/soundstreamhead2.fla b/tests/swfs/soundstreamhead2.fla new file mode 100644 index 000000000..cee0cf0ef Binary files /dev/null and b/tests/swfs/soundstreamhead2.fla differ diff --git a/tests/swfs/soundstreamhead2.swf b/tests/swfs/soundstreamhead2.swf new file mode 100644 index 000000000..cac3c5eaa Binary files /dev/null and b/tests/swfs/soundstreamhead2.swf differ