2021-06-07 18:45:51 -04:00
/*
* Copyright ( C ) 2021 Marijn Kruisselbrink < mek @ google . com >
*
* This program 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 2 of the License , or
* ( at your option ) any later version .
*
* This program 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 .
*
* You should have received a copy of the GNU General Public License along
* with this program ; if not , write to the Free Software Foundation , Inc . ,
* 51 Franklin Street , Fifth Floor , Boston , MA 02110 - 1301 USA .
*/
# include <boost/property_tree/json_parser.hpp>
# include <glibmm.h>
# include "pbd/compose.h"
2021-06-20 20:51:14 -04:00
# include "pbd/error.h"
2021-06-07 18:45:51 -04:00
# include "pbd/i18n.h"
# include "ardour/ffmpegfileimportable.h"
# include "ardour/filesystem_paths.h"
2021-06-20 20:51:14 -04:00
using namespace ARDOUR ;
2021-06-07 18:45:51 -04:00
static void
2021-06-20 20:51:14 -04:00
receive_stdout ( std : : string * out , const std : : string & data , size_t size )
{
2021-06-07 18:45:51 -04:00
* out + = data ;
}
2021-06-20 20:51:14 -04:00
FFMPEGFileImportableSource : : FFMPEGFileImportableSource ( const std : : string & path , int channel )
2021-06-07 18:45:51 -04:00
: _path ( path )
, _channel ( channel )
, _buffer ( 32768 )
, _ffmpeg_should_terminate ( 0 )
, _read_pos ( 0 )
2021-06-20 20:46:39 -04:00
, _ffmpeg_exec ( 0 )
2021-06-07 18:45:51 -04:00
{
std : : string ffprobe_exe , unused ;
if ( ! ArdourVideoToolPaths : : transcoder_exe ( unused , ffprobe_exe ) ) {
PBD : : error < < " FFMPEGFileImportableSource: Can't find ffprobe and ffmpeg " < < endmsg ;
2021-06-20 20:51:14 -04:00
throw failed_constructor ( ) ;
2021-06-07 18:45:51 -04:00
}
2021-06-20 20:51:14 -04:00
int a = 0 ;
char * * argp = ( char * * ) calloc ( 10 , sizeof ( char * ) ) ;
2021-06-07 18:45:51 -04:00
argp [ a + + ] = strdup ( ffprobe_exe . c_str ( ) ) ;
argp [ a + + ] = strdup ( _path . c_str ( ) ) ;
argp [ a + + ] = strdup ( " -show_streams " ) ;
argp [ a + + ] = strdup ( " -of " ) ;
argp [ a + + ] = strdup ( " json " ) ;
2022-04-01 12:12:47 -04:00
ARDOUR : : SystemExec * exec = new ARDOUR : : SystemExec ( ffprobe_exe , argp , true ) ;
2021-06-07 18:45:51 -04:00
PBD : : info < < " Probe command: { " < < exec - > to_s ( ) < < " } " < < endmsg ;
if ( exec - > start ( ) ) {
PBD : : error < < " FFMPEGFileImportableSource: External decoder (ffprobe) cannot be started. " < < endmsg ;
2021-06-20 20:46:39 -04:00
delete exec ;
2021-06-20 20:51:14 -04:00
throw failed_constructor ( ) ;
2021-06-07 18:45:51 -04:00
}
try {
PBD : : ScopedConnection c ;
2021-06-20 20:51:14 -04:00
std : : string ffprobe_output ;
2021-06-07 18:45:51 -04:00
exec - > ReadStdout . connect_same_thread ( c , boost : : bind ( & receive_stdout , & ffprobe_output , _1 , _2 ) ) ;
2021-06-20 20:46:39 -04:00
/* wait for ffprobe process to exit */
2021-06-20 20:51:14 -04:00
exec - > wait ( ) ;
2021-06-07 18:45:51 -04:00
namespace pt = boost : : property_tree ;
2021-06-20 20:51:14 -04:00
pt : : ptree root ;
2021-06-07 18:45:51 -04:00
std : : istringstream is ( ffprobe_output ) ;
2021-06-20 20:51:14 -04:00
2021-06-07 18:45:51 -04:00
pt : : read_json ( is , root ) ;
// TODO: Find the stream with the most channels, rather than whatever the first one is.
2021-06-20 20:51:14 -04:00
_channels = root . get < int > ( " streams..channels " ) ;
_length = root . get < int64_t > ( " streams..duration_ts " ) ;
_samplerate = root . get < int > ( " streams..sample_rate " ) ;
2021-06-07 18:45:51 -04:00
_natural_position = root . get < int64_t > ( " streams..start_pts " ) ;
2021-06-20 20:51:14 -04:00
_format_name = root . get < std : : string > ( " streams..codec_long_name " ) ;
2021-06-20 20:46:39 -04:00
delete exec ;
2021-06-07 18:45:51 -04:00
} catch ( . . . ) {
PBD : : error < < " FFMPEGFileImportableSource: Failed to read file metadata " < < endmsg ;
2021-06-20 20:46:39 -04:00
delete exec ;
2021-06-20 20:51:14 -04:00
throw failed_constructor ( ) ;
2021-06-07 18:45:51 -04:00
}
2021-06-20 20:51:14 -04:00
if ( _channel ! = ALL_CHANNELS & & ( _channel < 0 | | _channel > ( int ) channels ( ) ) ) {
PBD : : error < < string_compose ( " FFMPEGFileImportableSource: file only contains %1 channels; %2 is invalid as a channel number " , channels ( ) , _channel ) < < endmsg ;
throw failed_constructor ( ) ;
2021-06-07 18:45:51 -04:00
}
}
FFMPEGFileImportableSource : : ~ FFMPEGFileImportableSource ( )
{
2021-06-20 20:51:14 -04:00
reset ( ) ;
2021-06-07 18:45:51 -04:00
}
void
FFMPEGFileImportableSource : : seek ( samplepos_t pos )
{
if ( pos < _read_pos ) {
2021-06-20 20:51:14 -04:00
reset ( ) ;
2021-06-07 18:45:51 -04:00
}
if ( ! _ffmpeg_exec ) {
2021-06-20 20:51:14 -04:00
start_ffmpeg ( ) ;
2021-06-07 18:45:51 -04:00
}
while ( _read_pos < pos ) {
guint read_space = _buffer . read_space ( ) ;
if ( read_space = = 0 ) {
if ( ! _ffmpeg_exec - > is_running ( ) ) {
// FFMPEG quit, must have reached EOF.
PBD : : warning < < string_compose ( " FFMPEGFileImportableSource: Reached EOF while trying to seek to %1 " , pos ) < < endmsg ;
break ;
}
// TODO: don't just spin, but use some signalling
Glib : : usleep ( 1000 ) ;
continue ;
}
2021-06-20 20:51:14 -04:00
guint inc = std : : min < guint > ( read_space , pos - _read_pos ) ;
2021-06-07 18:45:51 -04:00
_buffer . increment_read_idx ( inc ) ;
_read_pos + = inc ;
}
}
samplecnt_t
FFMPEGFileImportableSource : : read ( Sample * dst , samplecnt_t nframes )
{
if ( ! _ffmpeg_exec ) {
2021-06-20 20:51:14 -04:00
start_ffmpeg ( ) ;
2021-06-07 18:45:51 -04:00
}
samplecnt_t total_read = 0 ;
while ( nframes > 0 ) {
guint read = _buffer . read ( dst + total_read , nframes ) ;
if ( read = = 0 ) {
if ( ! _ffmpeg_exec - > is_running ( ) ) {
// FFMPEG quit, must have reached EOF.
break ;
}
// TODO: don't just spin, but use some signalling
Glib : : usleep ( 1000 ) ;
continue ;
}
nframes - = read ;
total_read + = read ;
_read_pos + = read ;
}
return total_read ;
}
void
FFMPEGFileImportableSource : : start_ffmpeg ( )
{
std : : string ffmpeg_exe , unused ;
ArdourVideoToolPaths : : transcoder_exe ( ffmpeg_exe , unused ) ;
2021-06-20 20:51:14 -04:00
int a = 0 ;
char * * argp = ( char * * ) calloc ( 16 , sizeof ( char * ) ) ;
char tmp [ 32 ] ;
2021-06-07 18:45:51 -04:00
argp [ a + + ] = strdup ( ffmpeg_exe . c_str ( ) ) ;
argp [ a + + ] = strdup ( " -nostdin " ) ;
argp [ a + + ] = strdup ( " -i " ) ;
argp [ a + + ] = strdup ( _path . c_str ( ) ) ;
if ( _channel ! = ALL_CHANNELS ) {
argp [ a + + ] = strdup ( " -map_channel " ) ;
2021-06-20 20:51:14 -04:00
snprintf ( tmp , sizeof ( tmp ) , " 0.0.%d " , _channel ) ;
2021-06-07 18:45:51 -04:00
argp [ a + + ] = strdup ( tmp ) ;
}
argp [ a + + ] = strdup ( " -f " ) ;
# if G_BYTE_ORDER == G_LITTLE_ENDIAN
argp [ a + + ] = strdup ( " f32le " ) ;
# else
argp [ a + + ] = strdup ( " f32be " ) ;
# endif
argp [ a + + ] = strdup ( " - " ) ;
2022-04-01 12:12:47 -04:00
_ffmpeg_exec = new ARDOUR : : SystemExec ( ffmpeg_exe , argp , true ) ;
2021-06-07 18:45:51 -04:00
PBD : : info < < " Decode command: { " < < _ffmpeg_exec - > to_s ( ) < < " } " < < endmsg ;
if ( _ffmpeg_exec - > start ( ) ) {
PBD : : error < < " FFMPEGFileImportableSource: External decoder (ffmpeg) cannot be started. " < < endmsg ;
2021-06-20 20:51:14 -04:00
throw std : : runtime_error ( " Failed to start ffmpeg " ) ;
2021-06-07 18:45:51 -04:00
}
_ffmpeg_exec - > ReadStdout . connect_same_thread ( _ffmpeg_conn , boost : : bind ( & FFMPEGFileImportableSource : : did_read_data , this , _1 , _2 ) ) ;
}
void
FFMPEGFileImportableSource : : reset ( )
{
// TODO: actually signal did_read_data to unblock
2023-02-17 02:31:21 -05:00
_ffmpeg_should_terminate . store ( 1 ) ;
2021-06-07 18:45:51 -04:00
delete _ffmpeg_exec ;
2021-06-20 20:46:39 -04:00
_ffmpeg_exec = 0 ;
2021-06-07 18:45:51 -04:00
_ffmpeg_conn . disconnect ( ) ;
_buffer . reset ( ) ;
_read_pos = 0 ;
2023-02-17 02:31:21 -05:00
_ffmpeg_should_terminate . store ( 0 ) ;
2021-06-07 18:45:51 -04:00
}
void
FFMPEGFileImportableSource : : did_read_data ( std : : string data , size_t size )
{
// Prepend the left-over data from a previous chunk of received data to this chunk.
2021-06-20 20:51:14 -04:00
data = _leftover_data + data ;
samplecnt_t n_samples = data . length ( ) / sizeof ( float ) ;
2021-06-07 18:45:51 -04:00
// Stash leftover data.
2021-06-20 20:51:14 -04:00
_leftover_data = data . substr ( n_samples * sizeof ( float ) ) ;
2021-06-07 18:45:51 -04:00
const char * cur = data . data ( ) ;
while ( n_samples > 0 ) {
2023-02-17 02:31:21 -05:00
if ( _ffmpeg_should_terminate . load ( ) ) {
2021-06-07 18:45:51 -04:00
break ;
}
PBD : : RingBuffer < float > : : rw_vector wv ;
_buffer . get_write_vector ( & wv ) ;
if ( wv . len [ 0 ] = = 0 ) {
// TODO: don't just spin, but use some signalling
Glib : : usleep ( 1000 ) ;
continue ;
}
samplecnt_t written = 0 ;
for ( int i = 0 ; i < 2 ; + + i ) {
2021-06-20 20:51:14 -04:00
samplecnt_t cnt = std : : min < samplecnt_t > ( n_samples , wv . len [ i ] ) ;
2023-09-19 12:25:31 -04:00
if ( ! cnt ) {
2021-06-28 18:11:17 -04:00
break ;
}
2021-06-20 20:51:14 -04:00
memcpy ( wv . buf [ i ] , cur , cnt * sizeof ( float ) ) ;
2021-06-07 18:45:51 -04:00
written + = cnt ;
n_samples - = cnt ;
2021-06-20 20:51:14 -04:00
cur + = cnt * sizeof ( float ) ;
2021-06-07 18:45:51 -04:00
}
_buffer . increment_write_idx ( written ) ;
}
}