2016-07-05 17:44:02 +00:00
/*
This file is part of Telegram Desktop ,
the official desktop version of Telegram messaging app , see https : //telegram.org
Telegram Desktop is free software : you can redistribute it and / or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation , either version 3 of the License , or
( at your option ) any later version .
It is distributed in the hope that it will be useful ,
but WITHOUT ANY WARRANTY ; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
GNU General Public License for more details .
In addition , as a special exception , the copyright holders give permission
to link the code of portions of this program with the OpenSSL library .
Full license : https : //github.com/telegramdesktop/tdesktop/blob/master/LICENSE
2017-01-11 18:31:31 +00:00
Copyright ( c ) 2014 - 2017 John Preston , https : //desktop.telegram.org
2016-07-05 17:44:02 +00:00
*/
# include "media/media_child_ffmpeg_loader.h"
constexpr AVSampleFormat AudioToFormat = AV_SAMPLE_FMT_S16 ;
constexpr int64_t AudioToChannelLayout = AV_CH_LAYOUT_STEREO ;
constexpr int32 AudioToChannels = 2 ;
VideoSoundData : : ~ VideoSoundData ( ) {
if ( context ) {
avcodec_close ( context ) ;
avcodec_free_context ( & context ) ;
context = nullptr ;
}
}
2017-05-18 20:18:59 +00:00
ChildFFMpegLoader : : ChildFFMpegLoader ( std : : unique_ptr < VideoSoundData > & & data ) : AudioPlayerLoader ( FileLocation ( ) , QByteArray ( ) , base : : byte_vector ( ) )
2017-02-21 13:45:56 +00:00
, _parentData ( std : : move ( data ) ) {
2016-07-05 17:44:02 +00:00
_frame = av_frame_alloc ( ) ;
}
2016-07-21 17:35:55 +00:00
bool ChildFFMpegLoader : : open ( qint64 & position ) {
2016-07-05 17:44:02 +00:00
int res = 0 ;
char err [ AV_ERROR_MAX_STRING_SIZE ] = { 0 } ;
2017-04-30 11:34:23 +00:00
auto layout = _parentData - > context - > channel_layout ;
if ( ! layout ) {
auto channelsCount = _parentData - > context - > channels ;
switch ( channelsCount ) {
case 1 : layout = AV_CH_LAYOUT_MONO ; break ;
case 2 : layout = AV_CH_LAYOUT_STEREO ; break ;
default : LOG ( ( " Audio Error: Unknown channel layout for %1 channels. " ) . arg ( channelsCount ) ) ; break ;
}
}
2016-07-05 17:44:02 +00:00
_inputFormat = _parentData - > context - > sample_fmt ;
switch ( layout ) {
case AV_CH_LAYOUT_MONO :
switch ( _inputFormat ) {
case AV_SAMPLE_FMT_U8 :
case AV_SAMPLE_FMT_U8P : _format = AL_FORMAT_MONO8 ; _sampleSize = 1 ; break ;
case AV_SAMPLE_FMT_S16 :
case AV_SAMPLE_FMT_S16P : _format = AL_FORMAT_MONO16 ; _sampleSize = sizeof ( uint16 ) ; break ;
default :
_sampleSize = - 1 ; // convert needed
break ;
}
break ;
case AV_CH_LAYOUT_STEREO :
switch ( _inputFormat ) {
case AV_SAMPLE_FMT_U8 : _format = AL_FORMAT_STEREO8 ; _sampleSize = 2 ; break ;
case AV_SAMPLE_FMT_S16 : _format = AL_FORMAT_STEREO16 ; _sampleSize = 2 * sizeof ( uint16 ) ; break ;
default :
_sampleSize = - 1 ; // convert needed
break ;
}
break ;
default :
_sampleSize = - 1 ; // convert needed
break ;
}
if ( _parentData - > frequency ! = 44100 & & _parentData - > frequency ! = 48000 ) {
_sampleSize = - 1 ; // convert needed
}
if ( _sampleSize < 0 ) {
_swrContext = swr_alloc ( ) ;
if ( ! _swrContext ) {
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to swr_alloc for file '%1', data size '%2' " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) ) ;
2016-07-05 17:44:02 +00:00
return false ;
}
int64_t src_ch_layout = layout , dst_ch_layout = AudioToChannelLayout ;
_srcRate = _parentData - > frequency ;
AVSampleFormat src_sample_fmt = _inputFormat , dst_sample_fmt = AudioToFormat ;
2017-01-24 21:24:39 +00:00
_dstRate = ( _parentData - > frequency ! = 44100 & & _parentData - > frequency ! = 48000 ) ? Media : : Player : : kDefaultFrequency : _parentData - > frequency ;
2016-07-05 17:44:02 +00:00
av_opt_set_int ( _swrContext , " in_channel_layout " , src_ch_layout , 0 ) ;
av_opt_set_int ( _swrContext , " in_sample_rate " , _srcRate , 0 ) ;
av_opt_set_sample_fmt ( _swrContext , " in_sample_fmt " , src_sample_fmt , 0 ) ;
av_opt_set_int ( _swrContext , " out_channel_layout " , dst_ch_layout , 0 ) ;
av_opt_set_int ( _swrContext , " out_sample_rate " , _dstRate , 0 ) ;
av_opt_set_sample_fmt ( _swrContext , " out_sample_fmt " , dst_sample_fmt , 0 ) ;
if ( ( res = swr_init ( _swrContext ) ) < 0 ) {
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to swr_init for file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( res ) . arg ( av_make_error_string ( err , sizeof ( err ) , res ) ) ) ;
2016-07-05 17:44:02 +00:00
return false ;
}
_sampleSize = AudioToChannels * sizeof ( short ) ;
_parentData - > frequency = _dstRate ;
_parentData - > length = av_rescale_rnd ( _parentData - > length , _dstRate , _srcRate , AV_ROUND_UP ) ;
2016-07-21 17:35:55 +00:00
position = av_rescale_rnd ( position , _dstRate , _srcRate , AV_ROUND_DOWN ) ;
2016-07-05 17:44:02 +00:00
_format = AL_FORMAT_STEREO16 ;
_maxResampleSamples = av_rescale_rnd ( AVBlockSize / _sampleSize , _dstRate , _srcRate , AV_ROUND_UP ) ;
if ( ( res = av_samples_alloc_array_and_samples ( & _dstSamplesData , 0 , AudioToChannels , _maxResampleSamples , AudioToFormat , 0 ) ) < 0 ) {
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( res ) . arg ( av_make_error_string ( err , sizeof ( err ) , res ) ) ) ;
2016-07-05 17:44:02 +00:00
return false ;
}
}
return true ;
}
AudioPlayerLoader : : ReadResult ChildFFMpegLoader : : readMore ( QByteArray & result , int64 & samplesAdded ) {
2016-07-22 15:01:24 +00:00
int res ;
av_frame_unref ( _frame ) ;
res = avcodec_receive_frame ( _parentData - > context , _frame ) ;
if ( res > = 0 ) {
return readFromReadyFrame ( result , samplesAdded ) ;
}
if ( res = = AVERROR_EOF ) {
return ReadResult : : EndOfFile ;
} else if ( res ! = AVERROR ( EAGAIN ) ) {
char err [ AV_ERROR_MAX_STRING_SIZE ] = { 0 } ;
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to avcodec_receive_frame() file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( res ) . arg ( av_make_error_string ( err , sizeof ( err ) , res ) ) ) ;
2016-07-22 15:01:24 +00:00
return ReadResult : : Error ;
}
2016-07-05 17:44:02 +00:00
if ( _queue . isEmpty ( ) ) {
2016-07-05 17:44:22 +00:00
return _eofReached ? ReadResult : : EndOfFile : ReadResult : : Wait ;
2016-07-05 17:44:02 +00:00
}
2016-07-19 16:02:39 +00:00
AVPacket packet ;
FFMpeg : : packetFromDataWrap ( packet , _queue . dequeue ( ) ) ;
2016-07-05 17:44:22 +00:00
_eofReached = FFMpeg : : isNullPacket ( packet ) ;
if ( _eofReached ) {
2016-07-22 15:01:24 +00:00
avcodec_send_packet ( _parentData - > context , nullptr ) ; // drain
return ReadResult : : Ok ;
2016-07-05 17:44:22 +00:00
}
2016-07-22 15:01:24 +00:00
res = avcodec_send_packet ( _parentData - > context , & packet ) ;
if ( res < 0 ) {
2016-07-05 17:44:22 +00:00
FFMpeg : : freePacket ( & packet ) ;
2016-07-22 15:01:24 +00:00
char err [ AV_ERROR_MAX_STRING_SIZE ] = { 0 } ;
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to avcodec_send_packet() file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( res ) . arg ( av_make_error_string ( err , sizeof ( err ) , res ) ) ) ;
2016-08-14 18:57:23 +00:00
// There is a sample voice message where skipping such packet
// results in a crash (read_access to nullptr) in swr_convert().
2017-05-26 14:18:58 +00:00
if ( res = = AVERROR_INVALIDDATA ) {
return ReadResult : : NotYet ; // try to skip bad packet
}
2016-07-05 17:44:02 +00:00
return ReadResult : : Error ;
}
2016-07-22 15:01:24 +00:00
FFMpeg : : freePacket ( & packet ) ;
return ReadResult : : Ok ;
}
2016-07-05 17:44:02 +00:00
2016-07-22 15:01:24 +00:00
AudioPlayerLoader : : ReadResult ChildFFMpegLoader : : readFromReadyFrame ( QByteArray & result , int64 & samplesAdded ) {
int res = 0 ;
if ( _dstSamplesData ) { // convert needed
int64_t dstSamples = av_rescale_rnd ( swr_get_delay ( _swrContext , _srcRate ) + _frame - > nb_samples , _dstRate , _srcRate , AV_ROUND_UP ) ;
if ( dstSamples > _maxResampleSamples ) {
_maxResampleSamples = dstSamples ;
2017-01-09 13:12:53 +00:00
av_freep ( & _dstSamplesData [ 0 ] ) ;
2016-07-22 15:01:24 +00:00
if ( ( res = av_samples_alloc ( _dstSamplesData , 0 , AudioToChannels , _maxResampleSamples , AudioToFormat , 1 ) ) < 0 ) {
char err [ AV_ERROR_MAX_STRING_SIZE ] = { 0 } ;
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to av_samples_alloc for file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( res ) . arg ( av_make_error_string ( err , sizeof ( err ) , res ) ) ) ;
2016-07-05 17:44:02 +00:00
return ReadResult : : Error ;
}
}
2016-07-22 15:01:24 +00:00
if ( ( res = swr_convert ( _swrContext , _dstSamplesData , dstSamples , ( const uint8_t * * ) _frame - > extended_data , _frame - > nb_samples ) ) < 0 ) {
char err [ AV_ERROR_MAX_STRING_SIZE ] = { 0 } ;
2017-05-03 11:36:39 +00:00
LOG ( ( " Audio Error: Unable to swr_convert for file '%1', data size '%2', error %3, %4 " ) . arg ( _file . name ( ) ) . arg ( _data . size ( ) ) . arg ( res ) . arg ( av_make_error_string ( err , sizeof ( err ) , res ) ) ) ;
2016-07-22 15:01:24 +00:00
return ReadResult : : Error ;
}
int32 resultLen = av_samples_get_buffer_size ( 0 , AudioToChannels , res , AudioToFormat , 1 ) ;
result . append ( ( const char * ) _dstSamplesData [ 0 ] , resultLen ) ;
samplesAdded + = resultLen / _sampleSize ;
} else {
result . append ( ( const char * ) _frame - > extended_data [ 0 ] , _frame - > nb_samples * _sampleSize ) ;
samplesAdded + = _frame - > nb_samples ;
2016-07-05 17:44:02 +00:00
}
return ReadResult : : Ok ;
}
2016-07-19 16:02:39 +00:00
void ChildFFMpegLoader : : enqueuePackets ( QQueue < FFMpeg : : AVPacketDataWrap > & packets ) {
2017-02-21 13:45:56 +00:00
_queue + = std : : move ( packets ) ;
2016-07-05 17:44:02 +00:00
packets . clear ( ) ;
}
ChildFFMpegLoader : : ~ ChildFFMpegLoader ( ) {
2016-10-07 16:45:45 +00:00
auto queue = base : : take ( _queue ) ;
2016-07-19 16:02:39 +00:00
for ( auto & packetData : queue ) {
AVPacket packet ;
FFMpeg : : packetFromDataWrap ( packet , packetData ) ;
2016-07-05 17:44:22 +00:00
FFMpeg : : freePacket ( & packet ) ;
2016-07-05 17:44:02 +00:00
}
2016-07-13 17:34:57 +00:00
if ( _swrContext ) swr_free ( & _swrContext ) ;
2016-07-05 17:44:02 +00:00
if ( _dstSamplesData ) {
if ( _dstSamplesData [ 0 ] ) {
av_freep ( & _dstSamplesData [ 0 ] ) ;
}
av_freep ( & _dstSamplesData ) ;
}
av_frame_free ( & _frame ) ;
}