├── .github └── workflows │ └── ave.yml ├── .gitignore ├── Cargo.toml ├── LICENSE ├── README.md └── src ├── main.rs ├── sink.rs └── source.rs /.github/workflows/ave.yml: -------------------------------------------------------------------------------- 1 | name: ave 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | 8 | runs-on: ubuntu-latest 9 | 10 | steps: 11 | - uses: actions/checkout@v1 12 | - name: Install nasm 13 | run: | 14 | sudo apt-get install nasm 15 | - name: Install vpx 16 | env: 17 | VPX_TESTS: --disable-unit-tests --disable-examples 18 | VPX_INSTALL: --disable-install-docs 19 | run: | 20 | git clone --depth 1 https://github.com/webmproject/libvpx.git 21 | cd libvpx 22 | ./configure --enable-pic $VPX_TESTS $VPX_INSTALL --prefix=$HOME/vpx_dir 23 | make -j12 24 | make install 25 | - name: Install aom 26 | run: | 27 | git clone --depth 1 https://aomedia.googlesource.com/aom 28 | cd aom 29 | mkdir -p build 30 | cd build 31 | cmake -DCMAKE_INSTALL_PREFIX=$HOME/aom_dir \ 32 | -DBUILD_SHARED_LIBS=1 \ 33 | -DENABLE_TESTS=0 \ 34 | -DENABLE_EXAMPLES=0 \ 35 | .. 36 | make -j12 37 | make install 38 | - name: Install opus 39 | run: | 40 | git clone https://github.com/xiph/opus.git 41 | cd opus 42 | ./autogen.sh 43 | ./configure --prefix=$HOME/opus_dir 44 | make -j12 45 | make install 46 | - name: Run tests 47 | run: | 48 | export PKG_CONFIG_PATH=$HOME/vpx_dir/lib/pkgconfig:$PKG_CONFIG_PATH 49 | export PKG_CONFIG_PATH=$HOME/aom_dir/lib/pkgconfig:$PKG_CONFIG_PATH 50 | export LD_LIBRARY_PATH=$HOME/aom_dir/lib:$LD_LIBRARY_PATH 51 | export PKG_CONFIG_PATH=$HOME/opus_dir/lib/pkgconfig:$PKG_CONFIG_PATH 52 | export LD_LIBRARY_PATH=$HOME/opus_dir/lib:$LD_LIBRARY_PATH 53 | cargo test --all-features --verbose 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | **/*.rs.bk 3 | Cargo.lock 4 | *.swp 5 | *.swo 6 | .cargo 7 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ave" 3 | version = "0.1.0" 4 | authors = ["Luca Barbato "] 5 | 6 | [features] 7 | default = [] 8 | 9 | [dependencies] 10 | structopt = "0.3" 11 | log = "0.4" 12 | pretty_env_logger = "0.4" 13 | crossbeam-channel = "0.4" 14 | 15 | av-data = "0.2" 16 | av-codec = "0.2" 17 | av-format = "0.2" 18 | 19 | libvpx = { git = "https://github.com/rust-av/vpx-rs", features=["codec-trait"] } 20 | libopus = { git = "https://github.com/rust-av/opus-rs", features=["codec-trait"] } 21 | libaom = { git = "https://github.com/rust-av/aom-rs", features=["codec-trait"] } 22 | av-vorbis = { git = "https://github.com/rust-av/av-vorbis" } 23 | ivf = { git = "https://github.com/rust-av/ivf-rs"} 24 | 25 | matroska = { git = "https://github.com/rust-av/matroska" } 26 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Luca Barbato 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Audio-Video Encoder 2 | 3 | [![LICENSE](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) 4 | [![Actions Status](https://github.com/rust-av/ave/workflows/ave/badge.svg)](https://github.com/rust-av/ave/actions) 5 | 6 | **ave** is a simple encoding tool to test the [rust-av](https://github.com/rust-av) encoding API. 7 | 8 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | extern crate structopt; 2 | 3 | extern crate crossbeam_channel as channel; 4 | 5 | // Core crates 6 | extern crate av_codec as codec; 7 | extern crate av_data as data; 8 | extern crate av_format as format; 9 | 10 | // TODO: move those dependencies to av-formats 11 | // Demuxers 12 | extern crate ivf; 13 | extern crate matroska; 14 | 15 | // TODO: move those dependencies to av-codecs 16 | // Codecs 17 | extern crate av_vorbis as vorbis; 18 | extern crate libaom as aom; 19 | extern crate libopus as opus; 20 | extern crate libvpx as vpx; 21 | 22 | // Command line interface 23 | use std::path::PathBuf; 24 | use structopt::StructOpt; 25 | 26 | #[derive(StructOpt, Debug)] 27 | #[structopt(name = "ave")] 28 | /// Simple Audio Video Encoding tool 29 | struct Opt { 30 | /// Input file 31 | #[structopt(short = "i", parse(from_os_str))] 32 | input: PathBuf, 33 | /// Output file 34 | #[structopt(short = "o", parse(from_os_str))] 35 | output: PathBuf, 36 | } 37 | 38 | // TODO: Use fern? 39 | // Logging 40 | extern crate pretty_env_logger; 41 | #[macro_use] 42 | extern crate log; 43 | 44 | use format::common::GlobalInfo; 45 | 46 | mod sink; 47 | mod source; 48 | 49 | use sink::*; 50 | use source::*; 51 | 52 | use std::sync::Arc; 53 | use std::thread; 54 | 55 | use data::frame::ArcFrame; 56 | use data::packet::ArcPacket; 57 | 58 | use codec::common::CodecList; 59 | use codec::encoder; 60 | use codec::encoder::Context as EncoderCtx; 61 | 62 | use format::stream::Stream; 63 | 64 | use aom::encoder::AV1_DESCR; 65 | use opus::encoder::OPUS_DESCR; 66 | use vpx::encoder::VP9_DESCR; 67 | 68 | use channel as ch; 69 | 70 | fn main() { 71 | pretty_env_logger::init(); 72 | 73 | let opt = Opt::from_args(); 74 | 75 | let mut src = Source::from_path(&opt.input); 76 | 77 | let encoder_list = encoder::Codecs::from_list(&[VP9_DESCR, OPUS_DESCR, AV1_DESCR]); 78 | 79 | let mut info = GlobalInfo { 80 | duration: src.demuxer.info.duration.clone(), 81 | timebase: src.demuxer.info.timebase.clone(), 82 | streams: Vec::new(), 83 | }; 84 | 85 | info!("{:#?}", src.demuxer.info); 86 | 87 | // encoders -> muxer 88 | let (_encoders, recv_packet): (Vec>, ch::Receiver) = { 89 | let decoders = &mut src.decoders; 90 | let demuxer = &src.demuxer; 91 | let (send_packet, recv_packet) = ch::unbounded::(); 92 | 93 | let encoders = decoders 94 | .iter_mut() 95 | .scan(&mut info, |info, dec| { 96 | let st = &demuxer.info.streams[*dec.0]; 97 | // TODO: stream selection and mapping 98 | if let Some(ref codec_id) = st.params.codec_id { 99 | if let Some(mut ctx) = EncoderCtx::by_name(&encoder_list, codec_id) { 100 | // Derive a default setup from the input codec parameters 101 | debug!("Setting up {} encoder", codec_id); 102 | ctx.set_params(&st.params).unwrap(); 103 | // Overrides here 104 | let _ = ctx 105 | .set_option("timebase", (*st.timebase.numer(), *st.timebase.denom())); 106 | ctx.configure().unwrap(); 107 | let mut stream = Stream::from_params( 108 | &ctx.get_params().unwrap(), 109 | st.timebase.clone(), 110 | ); 111 | 112 | stream.id = st.id; 113 | stream.index = st.id as usize; 114 | 115 | let idx = info.add_stream(stream); 116 | // decoder -> encoder 117 | let (send_frame, recv_frame) = ch::unbounded::(); 118 | 119 | (dec.1).1 = Some(send_frame); 120 | let send_packet = send_packet.clone(); 121 | let b = thread::Builder::new().name(format!("encoder-{}", codec_id)); 122 | let th = b 123 | .spawn(move || { 124 | debug!("Encoding thread"); 125 | while let Ok(frame) = recv_frame.recv() { 126 | debug!("Encoding {:?}", frame); 127 | let _ = ctx.send_frame(&frame).map_err(|e| { 128 | error!("ctx.send_frame: {:?}", e); 129 | e 130 | }); 131 | 132 | while let Some(mut pkt) = ctx 133 | .receive_packet() 134 | .map_err(|e| { 135 | use codec::error::*; 136 | match e { 137 | Error::MoreDataNeeded => (), 138 | _ => { 139 | error!("flush ctx.receive_packet: {:?}", e); 140 | } 141 | } 142 | e 143 | }) 144 | .ok() 145 | { 146 | pkt.stream_index = idx as isize; 147 | debug!("Encoded {:?}", pkt); 148 | 149 | send_packet.send(Arc::new(pkt)).unwrap(); 150 | } 151 | } 152 | 153 | ctx.flush() 154 | .map_err(|e| { 155 | error!("ctx flush: {:?}", e); 156 | e 157 | }) 158 | .unwrap(); 159 | while let Some(mut pkt) = ctx 160 | .receive_packet() 161 | .map_err(|e| { 162 | use codec::error::*; 163 | match e { 164 | Error::MoreDataNeeded => (), 165 | _ => { 166 | error!("flush ctx.receive_packet: {:?}", e); 167 | } 168 | } 169 | e 170 | }) 171 | .ok() 172 | { 173 | pkt.stream_index = idx as isize; 174 | 175 | send_packet.send(Arc::new(pkt)).unwrap(); 176 | } 177 | }) 178 | .unwrap(); 179 | debug!("Done"); 180 | Some(th) 181 | } else { 182 | None 183 | } 184 | } else { 185 | None 186 | } 187 | }) 188 | .collect(); 189 | 190 | (encoders, recv_packet) 191 | }; 192 | 193 | info!("Encoders set {:?}", info); 194 | 195 | let mut sink = Sink::from_path(&opt.output, info); 196 | 197 | let b = thread::Builder::new().name("decode".to_owned()); 198 | let th_src = b 199 | .spawn(move || while let Ok(_) = src.decode_one() {}) 200 | .unwrap(); 201 | 202 | 203 | let b = thread::Builder::new().name("mux".to_owned()); 204 | let th_mux = b 205 | .spawn(move || { 206 | while let Ok(pkt) = recv_packet.recv() { 207 | let _ = sink.write_packet(pkt); 208 | } 209 | let _ = sink.write_trailer(); 210 | }) 211 | .unwrap(); 212 | 213 | let _ = th_src.join(); 214 | let _ = th_mux.join(); 215 | } 216 | -------------------------------------------------------------------------------- /src/sink.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::Path; 3 | use std::sync::Arc; 4 | 5 | use data::packet::Packet; 6 | 7 | use format; 8 | use format::common::GlobalInfo; 9 | use format::muxer::Context as MuxerCtx; 10 | 11 | use ivf::muxer::IvfMuxer; 12 | use matroska::muxer::MkvMuxer; 13 | 14 | pub struct Sink { 15 | muxer: MuxerCtx, 16 | } 17 | 18 | impl Sink { 19 | pub fn from_path(path: &Path, info: GlobalInfo) -> Self { 20 | let output = File::create(path).unwrap(); 21 | 22 | let mut muxer = match path.to_owned().extension().unwrap().to_str() { 23 | Some("ivf") => MuxerCtx::new(Box::new(IvfMuxer::new()), Box::new(output)), 24 | Some("webm") => MuxerCtx::new(Box::new(MkvMuxer::webm()), Box::new(output)), 25 | _ => MuxerCtx::new(Box::new(MkvMuxer::matroska()), Box::new(output)), 26 | }; 27 | 28 | muxer.set_global_info(info).unwrap(); 29 | muxer.configure().unwrap(); 30 | muxer.write_header().unwrap(); 31 | 32 | Sink { muxer } 33 | } 34 | 35 | pub fn write_packet(&mut self, packet: Arc) -> format::error::Result { 36 | self.muxer.write_packet(packet) 37 | } 38 | 39 | pub fn write_trailer(&mut self) -> format::error::Result { 40 | self.muxer.write_trailer() 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/source.rs: -------------------------------------------------------------------------------- 1 | use std::fs::File; 2 | use std::path::Path; 3 | 4 | use codec::common::CodecList; 5 | use data::frame::ArcFrame; 6 | 7 | use codec::decoder::Codecs as Decoders; 8 | use codec::decoder::Context as DecoderCtx; 9 | 10 | use aom::decoder::AV1_DESCR as AV1_DEC; 11 | use format::buffer::AccReader; 12 | use format::demuxer::Context as DemuxerCtx; 13 | use format::demuxer::Event; 14 | use opus::decoder::OPUS_DESCR as OPUS_DEC; 15 | use vorbis::decoder::VORBIS_DESCR as VORBIS_DEC; 16 | use vpx::decoder::VP9_DESCR as VP9_DEC; 17 | 18 | use ivf::demuxer::IvfDemuxer; 19 | use matroska::demuxer::MkvDemuxer; 20 | use std::collections::HashMap; 21 | 22 | use channel::Sender; 23 | 24 | /// The source binds a single Demuxer 25 | /// to as many Decoders as the Streams 26 | pub struct Source { 27 | pub decoders: HashMap>)>, 28 | pub demuxer: DemuxerCtx, 29 | } 30 | 31 | impl Source { 32 | /// Creates a source from a path 33 | // TODO: 34 | // - make the codec list allocation external 35 | pub fn from_path(path: &Path) -> Self { 36 | let decoder_list = Decoders::from_list(&[VP9_DEC, OPUS_DEC, VORBIS_DEC, AV1_DEC]); 37 | let r = File::open(path).unwrap(); 38 | let ar = AccReader::new(r); 39 | 40 | let mut demuxer = match path.to_owned().extension().unwrap().to_str() { 41 | Some("ivf") => DemuxerCtx::new(Box::new(IvfDemuxer::new()), Box::new(ar)), 42 | _ => DemuxerCtx::new(Box::new(MkvDemuxer::new()), Box::new(ar)), 43 | }; 44 | 45 | demuxer 46 | .read_headers() 47 | .expect("Cannot parse the format headers"); 48 | 49 | let mut decoders: HashMap>)> = 50 | HashMap::with_capacity(demuxer.info.streams.len()); 51 | 52 | for st in &demuxer.info.streams { 53 | if let Some(ref codec_id) = st.params.codec_id { 54 | if let Some(mut ctx) = DecoderCtx::by_name(&decoder_list, codec_id) { 55 | if let Some(ref extradata) = st.params.extradata { 56 | ctx.set_extradata(extradata); 57 | } 58 | ctx.configure().expect("Codec configure failed"); 59 | decoders.insert(st.index, (ctx, None)); 60 | info!( 61 | "Registering {} for stream {} (id {})", 62 | codec_id, st.index, st.id 63 | ); 64 | } 65 | } 66 | } 67 | 68 | Source { decoders, demuxer } 69 | } 70 | 71 | pub fn decode_one(&mut self) -> Result<(), String> { 72 | let ref mut c = self.demuxer; 73 | let ref mut decs = self.decoders; 74 | match c.read_event() { 75 | Ok(event) => match event { 76 | Event::NewPacket(pkt) => { 77 | if pkt.stream_index >= 0 { 78 | let idx = pkt.stream_index as usize; 79 | if let Some(dec) = decs.get_mut(&idx) { 80 | debug!("Decoding packet at index {}", pkt.stream_index); 81 | // TODO report error 82 | dec.0.send_packet(&pkt).unwrap(); 83 | if let Some(frame) = dec.0.receive_frame().ok() { 84 | dec.1.as_mut().unwrap().send(frame).unwrap(); 85 | } 86 | Ok(()) 87 | } else { 88 | debug!("Skipping packet at index {}", pkt.stream_index); 89 | Ok(()) 90 | } 91 | } else { 92 | warn!("Spurious packet"); 93 | Ok(()) 94 | } 95 | } 96 | Event::Eof => Err("EOF".to_owned()), 97 | _ => { 98 | error!("Unsupported event {:?}", event); 99 | unimplemented!(); 100 | } 101 | }, 102 | Err(err) => { 103 | warn!("No more events {:?}", err); 104 | Err("TBD".to_owned()) 105 | } 106 | } 107 | } 108 | } 109 | --------------------------------------------------------------------------------