]>
Commit | Line | Data |
---|---|---|
1 | use nihav_core::muxers::*; | |
2 | ||
3 | const FLV_VCODECS: &[(&str, u8)] = &[ | |
4 | ("flv263", 2), | |
5 | ("flashsv", 3), | |
6 | ("vp6f", 4), | |
7 | ("vp6a", 5), | |
8 | ("flashsv2", 6), | |
9 | ("h264", AVC_ID) | |
10 | ]; | |
11 | ||
12 | const NO_CODEC: u8 = 0; | |
13 | const AVC_ID: u8 = 7; | |
14 | const AAC_ID: u8 = 10; | |
15 | ||
16 | fn find_audio_tag(cname: &'static str, rate: u32, channels: u8) -> MuxerResult<u8> { | |
17 | if channels != 1 && channels != 2 { | |
18 | return Err(MuxerError::InvalidArgument); | |
19 | } | |
20 | let tag = match cname { | |
21 | "flv-adpcm" => 1, | |
22 | "pcm" => 3, | |
23 | "mp3" => if rate != 8000 { 2 } else { return Ok(14); }, | |
24 | "asao" => { | |
25 | if channels != 1 { | |
26 | return Err(MuxerError::InvalidArgument); | |
27 | } | |
28 | match rate { | |
29 | 16000 => return Ok(4), | |
30 | 8000 => return Ok(5), | |
31 | _ => 6, | |
32 | } | |
33 | }, | |
34 | "alaw" => 7, | |
35 | "ulaw" => 8, | |
36 | "aac" => AAC_ID, | |
37 | "speex" => 11, | |
38 | _ => return Err(MuxerError::InvalidArgument), | |
39 | }; | |
40 | match rate { | |
41 | 5500 | 11025 | 22050 | 44100 => {}, | |
42 | _ => return Err(MuxerError::InvalidArgument), | |
43 | }; | |
44 | Ok(tag) | |
45 | } | |
46 | ||
47 | trait FLVPropertyWriter { | |
48 | fn write_property_num(&mut self, name: &str, val: f64) -> MuxerResult<()>; | |
49 | fn write_property_bool(&mut self, name: &str, val: bool) -> MuxerResult<()>; | |
50 | } | |
51 | ||
52 | impl<'a> FLVPropertyWriter for ByteWriter<'a> { | |
53 | fn write_property_num(&mut self, name: &str, val: f64) -> MuxerResult<()> { | |
54 | self.write_u16be(name.len() as u16)?; | |
55 | self.write_buf(name.as_bytes())?; | |
56 | self.write_byte(0)?; | |
57 | self.write_f64be(val)?; | |
58 | Ok(()) | |
59 | } | |
60 | fn write_property_bool(&mut self, name: &str, val: bool) -> MuxerResult<()> { | |
61 | self.write_u16be(name.len() as u16)?; | |
62 | self.write_buf(name.as_bytes())?; | |
63 | self.write_byte(1)?; | |
64 | self.write_byte(val as u8)?; | |
65 | Ok(()) | |
66 | } | |
67 | } | |
68 | ||
69 | macro_rules! write_packet { | |
70 | ($self: expr, $pkt_type: expr, $ts: expr, $code: block) => { | |
71 | let start = $self.bw.tell(); | |
72 | $self.bw.write_byte($pkt_type)?; | |
73 | $self.bw.write_u24be(0)?; | |
74 | $self.bw.write_u24be($ts)?; | |
75 | $self.bw.write_byte(($ts >> 24) as u8)?; | |
76 | $self.bw.write_u24be(0)?; | |
77 | ||
78 | $code | |
79 | ||
80 | let end = $self.bw.tell(); | |
81 | let size = end - start - 11; | |
82 | $self.bw.seek(SeekFrom::Start(start + 1))?; | |
83 | $self.bw.write_u24be(size as u32)?; | |
84 | $self.bw.seek(SeekFrom::Start(end))?; | |
85 | $self.bw.write_u32be((size + 11) as u32)?; | |
86 | } | |
87 | } | |
88 | ||
89 | struct FLVMuxer<'a> { | |
90 | bw: &'a mut ByteWriter<'a>, | |
91 | atag: u8, | |
92 | ahdr: u8, | |
93 | vtag: u8, | |
94 | vp6b: u8, | |
95 | time: u32, | |
96 | dpos: u64, | |
97 | } | |
98 | ||
99 | impl<'a> FLVMuxer<'a> { | |
100 | fn new(bw: &'a mut ByteWriter<'a>) -> Self { | |
101 | Self { | |
102 | bw, | |
103 | atag: NO_CODEC, | |
104 | ahdr: 0, | |
105 | vtag: NO_CODEC, | |
106 | vp6b: 0, | |
107 | time: 0, | |
108 | dpos: 0, | |
109 | } | |
110 | } | |
111 | fn write_metadata(&mut self, strmgr: &StreamManager) -> MuxerResult<()> { | |
112 | write_packet!(self, 18, 0, { | |
113 | self.bw.write_buf(b"\x02\x00\x0AonMetaData\x08\x00\x00\x00\x00")?; | |
114 | for stream in strmgr.iter() { | |
115 | match stream.get_info().get_properties() { | |
116 | NACodecTypeInfo::Video(ref vinfo) => { | |
117 | self.bw.write_property_num("width", vinfo.width as f64)?; | |
118 | self.bw.write_property_num("height", vinfo.height as f64)?; | |
119 | self.bw.write_property_num("videocodecid", self.vtag as f64)?; | |
120 | }, | |
121 | NACodecTypeInfo::Audio(ref ainfo) => { | |
122 | self.bw.write_property_num("audiosamplerate", ainfo.sample_rate as f64)?; | |
123 | self.bw.write_property_bool("stereo", ainfo.channels == 2)?; | |
124 | self.bw.write_property_num("audiocodecid", self.atag as f64)?; | |
125 | }, | |
126 | _ => {}, | |
127 | }; | |
128 | } | |
129 | self.bw.write_property_num("duration", 0.0)?; | |
130 | self.dpos = self.bw.tell() - 8; | |
131 | self.bw.write_u16be(0)?; | |
132 | self.bw.write_byte(9)?; | |
133 | }); | |
134 | ||
135 | Ok(()) | |
136 | } | |
137 | } | |
138 | ||
139 | impl<'a> MuxCore<'a> for FLVMuxer<'a> { | |
140 | fn create(&mut self, strmgr: &StreamManager) -> MuxerResult<()> { | |
141 | if strmgr.get_num_streams() == 0 || strmgr.get_num_streams() > 2 { | |
142 | return Err(MuxerError::InvalidArgument); | |
143 | } | |
144 | ||
145 | let mut astream = None; | |
146 | let mut vstream = None; | |
147 | for stream in strmgr.iter() { | |
148 | let cname = stream.get_info().get_name(); | |
149 | match stream.get_media_type() { | |
150 | StreamType::Video => { | |
151 | vstream = Some(stream.clone()); | |
152 | if self.vtag != NO_CODEC { | |
153 | return Err(MuxerError::InvalidArgument); | |
154 | } | |
155 | for &(name, tag) in FLV_VCODECS.iter() { | |
156 | if name == cname { | |
157 | self.vtag = tag; | |
158 | break; | |
159 | } | |
160 | } | |
161 | if self.vtag == NO_CODEC { | |
162 | return Err(MuxerError::UnsupportedFormat); | |
163 | } | |
164 | }, | |
165 | StreamType::Audio => { | |
166 | astream = Some(stream.clone()); | |
167 | if self.atag != NO_CODEC { | |
168 | return Err(MuxerError::InvalidArgument); | |
169 | } | |
170 | if let Some(ainfo) = stream.get_info().get_properties().get_audio_info() { | |
171 | self.atag = find_audio_tag(cname, ainfo.sample_rate, ainfo.channels)?; | |
172 | self.ahdr = (self.atag << 4) | | |
173 | (match ainfo.sample_rate { | |
174 | 5500 => 0, | |
175 | 11025 => 1, | |
176 | 22050 => 2, | |
177 | _ => 3, | |
178 | } << 2) | | |
179 | if ainfo.format.bits == 8 { 0 } else { 2 } | | |
180 | if ainfo.channels == 1 { 0 } else { 1 }; | |
181 | } else { | |
182 | return Err(MuxerError::InvalidArgument); | |
183 | } | |
184 | }, | |
185 | _ => return Err(MuxerError::UnsupportedFormat), | |
186 | }; | |
187 | } | |
188 | ||
189 | self.bw.write_buf(b"FLV\x01")?; | |
190 | let flags = 0x8 | if self.atag != NO_CODEC { 4 } else { 0 } | if self.vtag != NO_CODEC { 1 } else { 0 }; | |
191 | self.bw.write_byte(flags)?; | |
192 | self.bw.write_u32be(9)?; | |
193 | self.bw.write_u32be(0)?; | |
194 | ||
195 | self.write_metadata(strmgr)?; | |
196 | ||
197 | if let (true, Some(ref stream)) = (self.vtag == 4 || self.vtag == 5, &vstream) { | |
198 | if let Some(edata) = stream.get_info().get_extradata() { | |
199 | if !edata.is_empty() { | |
200 | self.vp6b = edata[0]; | |
201 | } | |
202 | } | |
203 | } | |
204 | ||
205 | if let (true, Some(stream)) = (self.vtag == AVC_ID, vstream) { | |
206 | if let Some(edata) = stream.get_info().get_extradata() { | |
207 | validate!(edata.len() > 4); | |
208 | write_packet!(self, 9, 0, { | |
209 | self.bw.write_byte(0x57)?; | |
210 | self.bw.write_byte(0x00)?; | |
211 | self.bw.write_u24be(0)?; | |
212 | self.bw.write_buf(&edata[4..])?; | |
213 | }); | |
214 | } | |
215 | } | |
216 | if let (true, Some(stream)) = (self.atag == AAC_ID, astream) { | |
217 | if let Some(edata) = stream.get_info().get_extradata() { | |
218 | write_packet!(self, 8, 0, { | |
219 | self.bw.write_byte(self.ahdr)?; | |
220 | self.bw.write_byte(0x00)?; | |
221 | self.bw.write_buf(&edata)?; | |
222 | }); | |
223 | } | |
224 | } | |
225 | ||
226 | Ok(()) | |
227 | } | |
228 | fn mux_frame(&mut self, _strmgr: &StreamManager, pkt: NAPacket) -> MuxerResult<()> { | |
229 | let stream = pkt.get_stream(); | |
230 | let pts = pkt.get_pts().unwrap_or(0); | |
231 | let ms = NATimeInfo::ts_to_time(pts, 1000, pkt.ts.tb_num, pkt.ts.tb_den) as u32; | |
232 | self.time = self.time.max(ms); | |
233 | match stream.get_media_type() { | |
234 | StreamType::Video => { | |
235 | write_packet!(self, 9, ms, { | |
236 | let hdr = self.vtag | if pkt.keyframe { 0x10 } else { 0x20 }; | |
237 | self.bw.write_byte(hdr)?; | |
238 | match self.vtag { | |
239 | 4 | 5 => { | |
240 | self.bw.write_byte(self.vp6b)?; | |
241 | }, | |
242 | AVC_ID => { | |
243 | self.bw.write_byte(1)?; | |
244 | let cms = NATimeInfo::ts_to_time(pkt.get_pts().unwrap_or(pts), 1000, pkt.ts.tb_num, pkt.ts.tb_den) as u32; | |
245 | let cts = cms.wrapping_sub(ms) << 8 >> 8; | |
246 | self.bw.write_u24be(cts)?; | |
247 | }, | |
248 | _ => {}, | |
249 | }; | |
250 | self.bw.write_buf(&pkt.get_buffer())?; | |
251 | }); | |
252 | }, | |
253 | StreamType::Audio => { | |
254 | write_packet!(self, 8, ms, { | |
255 | self.bw.write_byte(self.ahdr)?; | |
256 | if self.atag == AAC_ID { | |
257 | self.bw.write_byte(1)?; | |
258 | } | |
259 | self.bw.write_buf(&pkt.get_buffer())?; | |
260 | }); | |
261 | }, | |
262 | _ => return Err(MuxerError::InvalidData), | |
263 | }; | |
264 | Ok(()) | |
265 | } | |
266 | fn flush(&mut self) -> MuxerResult<()> { | |
267 | Ok(()) | |
268 | } | |
269 | fn end(&mut self) -> MuxerResult<()> { | |
270 | self.bw.seek(SeekFrom::Start(self.dpos))?; | |
271 | self.bw.write_f64be((self.time as f64) / 1000.0)?; | |
272 | Ok(()) | |
273 | } | |
274 | } | |
275 | ||
276 | impl<'a> NAOptionHandler for FLVMuxer<'a> { | |
277 | fn get_supported_options(&self) -> &[NAOptionDefinition] { &[] } | |
278 | fn set_options(&mut self, _options: &[NAOption]) { } | |
279 | fn query_option_value(&self, _name: &str) -> Option<NAValue> { None } | |
280 | } | |
281 | ||
282 | pub struct FLVMuxerCreator {} | |
283 | ||
284 | impl MuxerCreator for FLVMuxerCreator { | |
285 | fn new_muxer<'a>(&self, bw: &'a mut ByteWriter<'a>) -> Box<dyn MuxCore<'a> + 'a> { | |
286 | Box::new(FLVMuxer::new(bw)) | |
287 | } | |
288 | fn get_name(&self) -> &'static str { "flv" } | |
289 | fn get_capabilities(&self) -> MuxerCapabilities { MuxerCapabilities::SingleVideoAndAudio("any", "any") } | |
290 | } | |
291 | ||
292 | #[cfg(test)] | |
293 | mod test { | |
294 | use nihav_core::codecs::*; | |
295 | use nihav_core::demuxers::*; | |
296 | use nihav_core::muxers::*; | |
297 | use nihav_codec_support::test::enc_video::*; | |
298 | use crate::*; | |
299 | ||
300 | #[test] | |
301 | fn test_flv_muxer() { | |
302 | let mut dmx_reg = RegisteredDemuxers::new(); | |
303 | flash_register_all_demuxers(&mut dmx_reg); | |
304 | // sample: https://samples.mplayerhq.hu/A-codecs/Nelly_Moser/input.flv | |
305 | let dec_config = DecoderTestParams { | |
306 | demuxer: "flv", | |
307 | in_name: "assets/Flash/input.flv", | |
308 | limit: None, | |
309 | stream_type: StreamType::None, | |
310 | dmx_reg, dec_reg: RegisteredDecoders::new(), | |
311 | }; | |
312 | let mut mux_reg = RegisteredMuxers::new(); | |
313 | flash_register_all_muxers(&mut mux_reg); | |
314 | /*let enc_config = EncoderTestParams { | |
315 | muxer: "flv", | |
316 | enc_name: "", | |
317 | out_name: "muxed.flv", | |
318 | mux_reg, enc_reg: RegisteredEncoders::new(), | |
319 | }; | |
320 | test_remuxing(&dec_config, &enc_config);*/ | |
321 | test_remuxing_md5(&dec_config, "flv", &mux_reg, | |
322 | [0xc777b605, 0x5777919d, 0x47996fe8, 0xf5e8d64f]); | |
323 | } | |
324 | } |