|
| 1 | +//! Decodes a raw H264 video binary into a `Vec` of YUV420P images. |
| 2 | +#![allow(unused)] |
| 3 | +use std::convert::AsRef; |
| 4 | +use std::path::{PathBuf, Path}; |
| 5 | +use std::ffi::{CStr, CString}; |
| 6 | +use std::os::raw::{c_char, c_int}; |
| 7 | +use libc::{size_t, c_float, c_void}; |
| 8 | +use ffmpeg_dev::sys::{ |
| 9 | + self, |
| 10 | + AVFrame, |
| 11 | + AVDictionary, |
| 12 | + AVCodec, |
| 13 | + AVCodecContext, |
| 14 | + AVStream, |
| 15 | + AVPacket, |
| 16 | + AVFormatContext, |
| 17 | + AVOutputFormat, |
| 18 | + AVCodecParameters, |
| 19 | + AVCodecParserContext, |
| 20 | + AVMediaType, |
| 21 | + AVMediaType_AVMEDIA_TYPE_UNKNOWN as AVMEDIA_TYPE_UNKNOWN, |
| 22 | + AVMediaType_AVMEDIA_TYPE_VIDEO as AVMEDIA_TYPE_VIDEO, |
| 23 | + AVMediaType_AVMEDIA_TYPE_AUDIO as AVMEDIA_TYPE_AUDIO, |
| 24 | + AVMediaType_AVMEDIA_TYPE_DATA as AVMEDIA_TYPE_DATA, |
| 25 | + AVMediaType_AVMEDIA_TYPE_SUBTITLE as AVMEDIA_TYPE_SUBTITLE, |
| 26 | + AVMediaType_AVMEDIA_TYPE_ATTACHMENT as AVMEDIA_TYPE_ATTACHMENT, |
| 27 | + AVMediaType_AVMEDIA_TYPE_NB as AVMEDIA_TYPE_NB, |
| 28 | + AVFMT_NOFILE, |
| 29 | + AVIO_FLAG_WRITE, |
| 30 | + AVRounding_AV_ROUND_NEAR_INF as AV_ROUND_NEAR_INF, |
| 31 | + AVRounding_AV_ROUND_PASS_MINMAX as AV_ROUND_PASS_MINMAX, |
| 32 | + AVCodecID_AV_CODEC_ID_H264 as AV_CODEC_ID_H264, |
| 33 | + AV_INPUT_BUFFER_PADDING_SIZE, |
| 34 | +}; |
| 35 | + |
| 36 | +// TODO: Use defs from `ffmpeg_dev::extra::defs` over hardcoded values. |
| 37 | +pub const NOPTS_VALUE: i64 = -9223372036854775808; |
| 38 | +pub const AVERROR_EAGAIN: i32 = 35; |
| 39 | + |
| 40 | +fn c_str(s: &str) -> CString { |
| 41 | + CString::new(s).expect("str to c str") |
| 42 | +} |
| 43 | + |
| 44 | +pub struct RawYuv420p { |
| 45 | + pub width: u32, |
| 46 | + pub height: u32, |
| 47 | + pub bufsize: i32, |
| 48 | + pub linesize: [i32; 4], |
| 49 | + pub data: [*mut u8; 4], |
| 50 | +} |
| 51 | + |
| 52 | +impl Drop for RawYuv420p { |
| 53 | + fn drop(&mut self) { |
| 54 | + unsafe { |
| 55 | + if !self.data[0].is_null() { |
| 56 | + sys::av_free(self.data[0] as *mut c_void); |
| 57 | + } |
| 58 | + }; |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | + |
| 63 | +impl RawYuv420p { |
| 64 | + pub fn luma_size(&self) -> u32 { |
| 65 | + self.width * self.height |
| 66 | + } |
| 67 | + pub fn chroma_size(&self) -> u32 { |
| 68 | + self.width * self.height / 4 |
| 69 | + } |
| 70 | + pub unsafe fn to_vec(&self) -> Vec<u8> { |
| 71 | + let mut output = Vec::<u8>::new(); |
| 72 | + let ptr = self.data[0]; |
| 73 | + for i in 0 .. self.bufsize as usize { |
| 74 | + let val = ptr.add(i); |
| 75 | + let val = *val; |
| 76 | + output.push(val); |
| 77 | + } |
| 78 | + output |
| 79 | + } |
| 80 | + pub unsafe fn save(&self, path: &str) { |
| 81 | + println!( |
| 82 | + "ffplay -s {}x{} -pix_fmt yuv420p {}", |
| 83 | + self.width, |
| 84 | + self.height, |
| 85 | + path, |
| 86 | + ); |
| 87 | + std::fs::write(path, self.to_vec()); |
| 88 | + } |
| 89 | + pub unsafe fn new(width: u32, height: u32) -> Self { |
| 90 | + use sys::{ |
| 91 | + AVPixelFormat_AV_PIX_FMT_YUV420P as AV_PIX_FMT_YUV420P |
| 92 | + }; |
| 93 | + let pix_fmt: sys::AVPixelFormat = AV_PIX_FMT_YUV420P; |
| 94 | + let mut linesize = [0i32; 4]; |
| 95 | + let mut data = [ |
| 96 | + std::ptr::null_mut(), |
| 97 | + std::ptr::null_mut(), |
| 98 | + std::ptr::null_mut(), |
| 99 | + std::ptr::null_mut(), |
| 100 | + ]; |
| 101 | + let bufsize = sys::av_image_alloc( |
| 102 | + data.as_mut_ptr(), |
| 103 | + linesize.as_mut_ptr(), |
| 104 | + width as i32, |
| 105 | + height as i32, |
| 106 | + pix_fmt, |
| 107 | + 1, |
| 108 | + ); |
| 109 | + RawYuv420p { |
| 110 | + width, |
| 111 | + height, |
| 112 | + bufsize, |
| 113 | + linesize, |
| 114 | + data, |
| 115 | + } |
| 116 | + } |
| 117 | + pub unsafe fn fill_from_frame(&mut self, frame: *mut AVFrame) { |
| 118 | + use sys::{ |
| 119 | + AVPixelFormat_AV_PIX_FMT_YUV420P as AV_PIX_FMT_YUV420P |
| 120 | + }; |
| 121 | + assert!(!frame.is_null()); |
| 122 | + assert!((*frame).format == AV_PIX_FMT_YUV420P); |
| 123 | + sys::av_image_copy( |
| 124 | + self.data.as_mut_ptr(), |
| 125 | + self.linesize.as_mut_ptr(), |
| 126 | + (*frame).data.as_mut_ptr() as *mut *const u8, |
| 127 | + (*frame).linesize.as_ptr(), |
| 128 | + (*frame).format, |
| 129 | + (*frame).width, |
| 130 | + (*frame).height, |
| 131 | + ); |
| 132 | + } |
| 133 | +} |
| 134 | + |
| 135 | + |
| 136 | +unsafe fn decode_h264_video( |
| 137 | + source: Vec<u8>, |
| 138 | +) -> Vec<RawYuv420p> { |
| 139 | + // HELPERS |
| 140 | + unsafe fn decode( |
| 141 | + dec_ctx: *mut AVCodecContext, |
| 142 | + frame: *mut AVFrame, |
| 143 | + pkt: *mut AVPacket, |
| 144 | + output: &mut Vec<RawYuv420p>, |
| 145 | + ) { |
| 146 | + let mut buf = vec![0u8; 1024]; |
| 147 | + let mut ret = sys::avcodec_send_packet(dec_ctx, pkt); |
| 148 | + assert!(ret >= 0); |
| 149 | + while ret >= 0 { |
| 150 | + ret = sys::avcodec_receive_frame(dec_ctx, frame); |
| 151 | + if (ret < 0) { |
| 152 | + return; |
| 153 | + } |
| 154 | + let done = { |
| 155 | + ret == ffmpeg_dev::extra::defs::averror(ffmpeg_dev::extra::defs::eagain()) || |
| 156 | + ret == ffmpeg_dev::extra::defs::averror_eof() |
| 157 | + }; |
| 158 | + if done { |
| 159 | + return; |
| 160 | + } |
| 161 | + assert!(ret >= 0); |
| 162 | + // WRITE DECODED FRAME |
| 163 | + let mut decoded = RawYuv420p::new((*frame).width as u32, (*frame).height as u32); |
| 164 | + decoded.fill_from_frame(frame); |
| 165 | + output.push(decoded); |
| 166 | + } |
| 167 | + } |
| 168 | + // I/O |
| 169 | + let mut f = source; |
| 170 | + // MISC |
| 171 | + const INBUF_SIZE: u32 = 4096; |
| 172 | + // SETUP AV STATE |
| 173 | + let mut codec: *mut AVCodec = sys::avcodec_find_decoder(AV_CODEC_ID_H264); |
| 174 | + assert!(!codec.is_null()); |
| 175 | + let mut parser: *mut AVCodecParserContext = sys::av_parser_init((*codec).id as i32); |
| 176 | + let mut c: *mut AVCodecContext = sys::avcodec_alloc_context3(codec); |
| 177 | + assert!(!c.is_null()); |
| 178 | + let mut frame: *mut AVFrame = sys::av_frame_alloc(); |
| 179 | + let mut inbuf: Vec<u8> = vec![0; (INBUF_SIZE + AV_INPUT_BUFFER_PADDING_SIZE) as usize]; |
| 180 | + let mut pkt: *mut AVPacket = sys::av_packet_alloc(); |
| 181 | + assert!(!pkt.is_null()); |
| 182 | + // OPEN |
| 183 | + assert!(sys::avcodec_open2(c, codec, std::ptr::null_mut()) >= 0); |
| 184 | + let mut output = Vec::<RawYuv420p>::new(); |
| 185 | + let mut eof = false; |
| 186 | + while eof == false { |
| 187 | + let inbuf_size = { |
| 188 | + if f.len() < INBUF_SIZE as usize { |
| 189 | + f.len() |
| 190 | + } else { |
| 191 | + INBUF_SIZE as usize |
| 192 | + } |
| 193 | + }; |
| 194 | + let inbuf = f |
| 195 | + .drain(0..inbuf_size) |
| 196 | + .collect::<Vec<u8>>(); |
| 197 | + let mut inbuf_size = inbuf_size as isize; |
| 198 | + if inbuf.is_empty() { |
| 199 | + eof = true; |
| 200 | + break; |
| 201 | + } |
| 202 | + while inbuf_size > 0 { |
| 203 | + let ret = sys::av_parser_parse2( |
| 204 | + parser, |
| 205 | + c, |
| 206 | + &mut (*pkt).data, |
| 207 | + &mut (*pkt).size, |
| 208 | + inbuf.as_ptr(), |
| 209 | + inbuf.len() as i32, |
| 210 | + NOPTS_VALUE, |
| 211 | + NOPTS_VALUE, |
| 212 | + 0, |
| 213 | + ); |
| 214 | + assert!(ret >= 0); |
| 215 | + inbuf_size = inbuf_size - (ret as isize); |
| 216 | + if (*pkt).size > 0 { |
| 217 | + decode(c, frame, pkt, &mut output); |
| 218 | + } |
| 219 | + } |
| 220 | + } |
| 221 | + // FLUSH THE DECODER |
| 222 | + decode(c, frame, std::ptr::null_mut(), &mut output); |
| 223 | + // CLEANUP |
| 224 | + sys::av_parser_close(parser); |
| 225 | + sys::avcodec_free_context(&mut c); |
| 226 | + sys::av_frame_free(&mut frame); |
| 227 | + sys::av_packet_free(&mut pkt); |
| 228 | + // DONE |
| 229 | + output |
| 230 | +} |
| 231 | + |
| 232 | + |
| 233 | +/////////////////////////////////////////////////////////////////////////////// |
| 234 | +// MAIN |
| 235 | +/////////////////////////////////////////////////////////////////////////////// |
| 236 | + |
| 237 | +fn main() { |
| 238 | + let path = "assets/test/test.h264"; |
| 239 | + assert!(PathBuf::from(path).exists()); |
| 240 | + let source = std::fs::read(path).expect("read source file"); |
| 241 | + unsafe { |
| 242 | + decode_h264_video(source); |
| 243 | + }; |
| 244 | +} |
| 245 | + |
0 commit comments