# include "tools/replay/framereader.h"
# include <map>
# include <memory>
# include <tuple>
# include <utility>
# include "common/util.h"
# include "third_party/libyuv/include/libyuv.h"
# include "tools/replay/util.h"
# ifdef __APPLE__
# define HW_DEVICE_TYPE AV_HWDEVICE_TYPE_VIDEOTOOLBOX
# define HW_PIX_FMT AV_PIX_FMT_VIDEOTOOLBOX
# else
# define HW_DEVICE_TYPE AV_HWDEVICE_TYPE_CUDA
# define HW_PIX_FMT AV_PIX_FMT_CUDA
# endif
namespace {
enum AVPixelFormat get_hw_format ( AVCodecContext * ctx , const enum AVPixelFormat * pix_fmts ) {
enum AVPixelFormat * hw_pix_fmt = reinterpret_cast < enum AVPixelFormat * > ( ctx - > opaque ) ;
for ( const enum AVPixelFormat * p = pix_fmts ; * p ! = - 1 ; p + + ) {
if ( * p = = * hw_pix_fmt ) return * p ;
}
rWarning ( " Please run replay with the --no-hw-decoder flag! " ) ;
* hw_pix_fmt = AV_PIX_FMT_NONE ;
return AV_PIX_FMT_YUV420P ;
}
struct DecoderManager {
VideoDecoder * acquire ( CameraType type , AVCodecParameters * codecpar , bool hw_decoder ) {
auto key = std : : tuple ( type , codecpar - > width , codecpar - > height ) ;
std : : unique_lock lock ( mutex_ ) ;
if ( auto it = decoders_ . find ( key ) ; it ! = decoders_ . end ( ) ) {
return it - > second . get ( ) ;
}
auto decoder = std : : make_unique < VideoDecoder > ( ) ;
if ( ! decoder - > open ( codecpar , hw_decoder ) ) {
decoder . reset ( nullptr ) ;
}
decoders_ [ key ] = std : : move ( decoder ) ;
return decoders_ [ key ] . get ( ) ;
}
std : : mutex mutex_ ;
std : : map < std : : tuple < CameraType , int , int > , std : : unique_ptr < VideoDecoder > > decoders_ ;
} ;
DecoderManager decoder_manager ;
} // namespace
FrameReader : : FrameReader ( ) {
av_log_set_level ( AV_LOG_QUIET ) ;
}
FrameReader : : ~ FrameReader ( ) {
if ( input_ctx ) avformat_close_input ( & input_ctx ) ;
}
bool FrameReader : : load ( CameraType type , const std : : string & url , bool no_hw_decoder , std : : atomic < bool > * abort , bool local_cache , int chunk_size , int retries ) {
auto local_file_path = url . find ( " https:// " ) = = 0 ? cacheFilePath ( url ) : url ;
if ( ! util : : file_exists ( local_file_path ) ) {
FileReader f ( local_cache , chunk_size , retries ) ;
if ( f . read ( url , abort ) . empty ( ) ) {
return false ;
}
}
return loadFromFile ( type , local_file_path , no_hw_decoder , abort ) ;
}
bool FrameReader : : loadFromFile ( CameraType type , const std : : string & file , bool no_hw_decoder , std : : atomic < bool > * abort ) {
if ( avformat_open_input ( & input_ctx , file . c_str ( ) , nullptr , nullptr ) ! = 0 | |
avformat_find_stream_info ( input_ctx , nullptr ) < 0 ) {
rError ( " Failed to open input file or find video stream " ) ;
return false ;
}
input_ctx - > probesize = 10 * 1024 * 1024 ; // 10MB
decoder_ = decoder_manager . acquire ( type , input_ctx - > streams [ 0 ] - > codecpar , ! no_hw_decoder ) ;
if ( ! decoder_ ) {
return false ;
}
width = decoder_ - > width ;
height = decoder_ - > height ;
AVPacket pkt ;
packets_info . reserve ( 60 * 20 ) ; // 20fps, one minute
while ( ! ( abort & & * abort ) & & av_read_frame ( input_ctx , & pkt ) = = 0 ) {
packets_info . emplace_back ( PacketInfo { . flags = pkt . flags , . pos = pkt . pos } ) ;
av_packet_unref ( & pkt ) ;
}
avio_seek ( input_ctx - > pb , 0 , SEEK_SET ) ;
return ! packets_info . empty ( ) ;
}
bool FrameReader : : get ( int idx , VisionBuf * buf ) {
if ( ! buf | | idx < 0 | | idx > = packets_info . size ( ) ) {
return false ;
}
return decoder_ - > decode ( this , idx , buf ) ;
}
// class VideoDecoder
VideoDecoder : : VideoDecoder ( ) {
av_frame_ = av_frame_alloc ( ) ;
hw_frame_ = av_frame_alloc ( ) ;
}
VideoDecoder : : ~ VideoDecoder ( ) {
if ( hw_device_ctx ) av_buffer_unref ( & hw_device_ctx ) ;
if ( decoder_ctx ) avcodec_free_context ( & decoder_ctx ) ;
av_frame_free ( & av_frame_ ) ;
av_frame_free ( & hw_frame_ ) ;
}
bool VideoDecoder : : open ( AVCodecParameters * codecpar , bool hw_decoder ) {
const AVCodec * decoder = avcodec_find_decoder ( codecpar - > codec_id ) ;
if ( ! decoder ) return false ;
decoder_ctx = avcodec_alloc_context3 ( decoder ) ;
if ( ! decoder_ctx | | avcodec_parameters_to_context ( decoder_ctx , codecpar ) ! = 0 ) {
rError ( " Failed to allocate or initialize codec context " ) ;
return false ;
}
width = ( decoder_ctx - > width + 3 ) & ~ 3 ;
height = decoder_ctx - > height ;
if ( hw_decoder & & ! initHardwareDecoder ( HW_DEVICE_TYPE ) ) {
rWarning ( " No device with hardware decoder found. fallback to CPU decoding. " ) ;
}
if ( avcodec_open2 ( decoder_ctx , decoder , nullptr ) < 0 ) {
rError ( " Failed to open codec " ) ;
return false ;
}
return true ;
}
bool VideoDecoder : : initHardwareDecoder ( AVHWDeviceType hw_device_type ) {
const AVCodecHWConfig * config = nullptr ;
for ( int i = 0 ; ( config = avcodec_get_hw_config ( decoder_ctx - > codec , i ) ) ! = nullptr ; i + + ) {
if ( config - > methods & AV_CODEC_HW_CONFIG_METHOD_HW_DEVICE_CTX & & config - > device_type = = hw_device_type ) {
hw_pix_fmt = config - > pix_fmt ;
break ;
}
}
if ( ! config ) {
rWarning ( " Hardware configuration not found " ) ;
return false ;
}
int ret = av_hwdevice_ctx_create ( & hw_device_ctx , hw_device_type , nullptr , nullptr , 0 ) ;
if ( ret < 0 ) {
hw_pix_fmt = AV_PIX_FMT_NONE ;
rWarning ( " Failed to create specified HW device %d. " , ret ) ;
return false ;
}
decoder_ctx - > hw_device_ctx = av_buffer_ref ( hw_device_ctx ) ;
decoder_ctx - > opaque = & hw_pix_fmt ;
decoder_ctx - > get_format = get_hw_format ;
return true ;
}
bool VideoDecoder : : decode ( FrameReader * reader , int idx , VisionBuf * buf ) {
int from_idx = idx ;
if ( idx ! = reader - > prev_idx + 1 ) {
// seeking to the nearest key frame
for ( int i = idx ; i > = 0 ; - - i ) {
if ( reader - > packets_info [ i ] . flags & AV_PKT_FLAG_KEY ) {
from_idx = i ;
break ;
}
}
auto pos = reader - > packets_info [ from_idx ] . pos ;
int ret = avformat_seek_file ( reader - > input_ctx , 0 , pos , pos , pos , AVSEEK_FLAG_BYTE ) ;
if ( ret < 0 ) {
rError ( " Failed to seek to byte position %lld: %d " , pos , AVERROR ( ret ) ) ;
return false ;
}
avcodec_flush_buffers ( decoder_ctx ) ;
}
reader - > prev_idx = idx ;
bool result = false ;
AVPacket pkt ;
for ( int i = from_idx ; i < = idx ; + + i ) {
if ( av_read_frame ( reader - > input_ctx , & pkt ) = = 0 ) {
AVFrame * f = decodeFrame ( & pkt ) ;
if ( f & & i = = idx ) {
result = copyBuffer ( f , buf ) ;
}
av_packet_unref ( & pkt ) ;
}
}
return result ;
}
AVFrame * VideoDecoder : : decodeFrame ( AVPacket * pkt ) {
int ret = avcodec_send_packet ( decoder_ctx , pkt ) ;
if ( ret < 0 ) {
rError ( " Error sending a packet for decoding: %d " , ret ) ;
return nullptr ;
}
ret = avcodec_receive_frame ( decoder_ctx , av_frame_ ) ;
if ( ret ! = 0 ) {
rError ( " avcodec_receive_frame error: %d " , ret ) ;
return nullptr ;
}
if ( av_frame_ - > format = = hw_pix_fmt & & av_hwframe_transfer_data ( hw_frame_ , av_frame_ , 0 ) < 0 ) {
rError ( " error transferring frame data from GPU to CPU " ) ;
return nullptr ;
}
return ( av_frame_ - > format = = hw_pix_fmt ) ? hw_frame_ : av_frame_ ;
}
bool VideoDecoder : : copyBuffer ( AVFrame * f , VisionBuf * buf ) {
if ( hw_pix_fmt = = HW_PIX_FMT ) {
for ( int i = 0 ; i < height / 2 ; i + + ) {
memcpy ( buf - > y + ( i * 2 + 0 ) * buf - > stride , f - > data [ 0 ] + ( i * 2 + 0 ) * f - > linesize [ 0 ] , width ) ;
memcpy ( buf - > y + ( i * 2 + 1 ) * buf - > stride , f - > data [ 0 ] + ( i * 2 + 1 ) * f - > linesize [ 0 ] , width ) ;
memcpy ( buf - > uv + i * buf - > stride , f - > data [ 1 ] + i * f - > linesize [ 1 ] , width ) ;
}
} else {
libyuv : : I420ToNV12 ( f - > data [ 0 ] , f - > linesize [ 0 ] ,
f - > data [ 1 ] , f - > linesize [ 1 ] ,
f - > data [ 2 ] , f - > linesize [ 2 ] ,
buf - > y , buf - > stride ,
buf - > uv , buf - > stride ,
width , height ) ;
}
return true ;
}