You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
mlt/src/modules/kino/filehandler.cc

941 lines
19 KiB

/*
* filehandler.cc -- saving DV data into different file formats
* Copyright (C) 2000 Arne Schirmacher <arne@schirmacher.de>
*
* 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 "config.h"
extern "C" {
#include <framework/mlt_frame.h>
}
#include <string>
#include <iostream>
#include <sstream>
#include <iomanip>
using std::cerr;
using std::endl;
using std::ostringstream;
using std::setw;
using std::setfill;
#include <signal.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <assert.h>
#include <time.h>
#include <sys/time.h>
#include <string.h>
// libdv header files
#ifdef HAVE_LIBDV
#include <libdv/dv.h>
#endif
#include "filehandler.h"
#include "error.h"
#include "riff.h"
#include "avi.h"
FileTracker *FileTracker::instance = NULL;
FileTracker::FileTracker( ) : mode( CAPTURE_MOVIE_APPEND )
{
cerr << ">> Constructing File Capture tracker" << endl;
}
FileTracker::~FileTracker( )
{
cerr << ">> Destroying File Capture tracker" << endl;
}
FileTracker &FileTracker::GetInstance( )
{
if ( instance == NULL )
instance = new FileTracker();
return *instance;
}
void FileTracker::SetMode( FileCaptureMode mode )
{
this->mode = mode;
}
FileCaptureMode FileTracker::GetMode( )
{
return this->mode;
}
char *FileTracker::Get( int index )
{
return list[ index ];
}
void FileTracker::Add( const char *file )
{
if ( this->mode != CAPTURE_IGNORE )
{
cerr << ">>>> Registering " << file << " with the tracker" << endl;
list.push_back( strdup( file ) );
}
}
unsigned int FileTracker::Size( )
{
return list.size();
}
void FileTracker::Clear( )
{
while ( Size() > 0 )
{
free( list[ Size() - 1 ] );
list.pop_back( );
}
this->mode = CAPTURE_MOVIE_APPEND;
}
FileHandler::FileHandler() : done( false ), autoSplit( false ), maxFrameCount( 999999 ),
framesWritten( 0 ), filename( "" )
{
/* empty body */
}
FileHandler::~FileHandler()
{
/* empty body */
}
bool FileHandler::GetAutoSplit() const
{
return autoSplit;
}
bool FileHandler::GetTimeStamp() const
{
return timeStamp;
}
string FileHandler::GetBaseName() const
{
return base;
}
string FileHandler::GetExtension() const
{
return extension;
}
int FileHandler::GetMaxFrameCount() const
{
return maxFrameCount;
}
off_t FileHandler::GetMaxFileSize() const
{
return maxFileSize;
}
string FileHandler::GetFilename() const
{
return filename;
}
void FileHandler::SetAutoSplit( bool flag )
{
autoSplit = flag;
}
void FileHandler::SetTimeStamp( bool flag )
{
timeStamp = flag;
}
void FileHandler::SetBaseName( const string& s )
{
base = s;
}
void FileHandler::SetMaxFrameCount( int count )
{
assert( count >= 0 );
maxFrameCount = count;
}
void FileHandler::SetEveryNthFrame( int every )
{
assert ( every > 0 );
everyNthFrame = every;
}
void FileHandler::SetMaxFileSize( off_t size )
{
assert ( size >= 0 );
maxFileSize = size;
}
#if 0
void FileHandler::SetSampleFrame( const Frame& sample )
{
/* empty body */
}
#endif
bool FileHandler::Done()
{
return done;
}
#if 0
bool FileHandler::WriteFrame( const Frame& frame )
{
static TimeCode prevTimeCode;
TimeCode timeCode;
/* If the user wants autosplit, start a new file if a
new recording is detected. */
prevTimeCode.sec = -1;
frame.GetTimeCode( timeCode );
int time_diff = timeCode.sec - prevTimeCode.sec;
bool discontinuity = prevTimeCode.sec != -1 && ( time_diff > 1 || ( time_diff < 0 && time_diff > -59 ) );
if ( FileIsOpen() && GetAutoSplit() == true && ( frame.IsNewRecording() || discontinuity ) )
{
Close();
}
if ( FileIsOpen() == false )
{
string filename;
static int counter = 0;
if ( GetTimeStamp() == true )
{
ostringstream sb, sb2;
struct tm date;
string recDate;
if ( ! frame.GetRecordingDate( date ) )
{
struct timeval tv;
struct timezone tz;
gettimeofday( &tv, &tz );
localtime_r( static_cast< const time_t * >( &tv.tv_sec ), &date );
}
sb << setfill( '0' )
<< setw( 4 ) << date.tm_year + 1900 << '.'
<< setw( 2 ) << date.tm_mon + 1 << '.'
<< setw( 2 ) << date.tm_mday << '_'
<< setw( 2 ) << date.tm_hour << '-'
<< setw( 2 ) << date.tm_min << '-'
<< setw( 2 ) << date.tm_sec;
recDate = sb.str();
sb2 << GetBaseName() << recDate << GetExtension();
filename = sb2.str();
cerr << ">>> Trying " << filename << endl;
}
else
{
struct stat stats;
do
{
ostringstream sb;
sb << GetBaseName() << setfill( '0' ) << setw( 3 ) << ++ counter << GetExtension();
filename = sb.str();
cerr << ">>> Trying " << filename << endl;
}
while ( stat( filename.c_str(), &stats ) == 0 );
}
SetSampleFrame( frame );
if ( Create( filename ) == false )
{
cerr << ">>> Error creating file!" << endl;
return false;
}
framesWritten = 0;
framesToSkip = 0;
}
/* write frame */
if ( framesToSkip == 0 )
{
if ( 0 > Write( frame ) )
{
cerr << ">>> Error writing frame!" << endl;
return false;
}
framesToSkip = everyNthFrame;
++framesWritten;
}
framesToSkip--;
/* If the frame count is exceeded, close the current file.
If the autosplit flag is set, a new file will be created in the next iteration.
If the flag is not set, we are done. */
if ( ( GetMaxFrameCount() > 0 ) &&
( framesWritten >= GetMaxFrameCount() ) )
{
Close();
done = !GetAutoSplit();
}
/* If the file size could be exceeded by another frame, close the current file.
If the autosplit flag is set, a new file will be created on the next iteration.
If the flag is not set, we are done. */
/* not exact, but should be good enough to prevent going over. */
if ( FileIsOpen() )
{
AudioInfo info;
frame.GetAudioInfo( info );
if ( ( GetFileSize() > 0 ) && ( GetMaxFileSize() > 0 ) &&
( GetFileSize() + frame.GetFrameSize() + info.samples * 4 + 12 )
>= GetMaxFileSize() )
{ // 12 = sizeof chunk metadata
Close();
done = !GetAutoSplit();
}
}
prevTimeCode.sec = timeCode.sec;
return true;
}
#endif
RawHandler::RawHandler() : fd( -1 )
{
extension = ".dv";
}
RawHandler::~RawHandler()
{
Close();
}
bool RawHandler::FileIsOpen()
{
return fd != -1;
}
bool RawHandler::Create( const string& filename )
{
fd = open( filename.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_NONBLOCK, 0644 );
if ( fd != -1 )
{
FileTracker::GetInstance().Add( filename.c_str() );
this->filename = filename;
}
return ( fd != -1 );
}
#if 0
int RawHandler::Write( const Frame& frame )
{
int result = write( fd, frame.data, frame.GetFrameSize() );
return result;
}
#endif
int RawHandler::Close()
{
if ( fd != -1 )
{
close( fd );
fd = -1;
}
return 0;
}
off_t RawHandler::GetFileSize()
{
struct stat file_status;
fstat( fd, &file_status );
return file_status.st_size;
}
int RawHandler::GetTotalFrames()
{
return GetFileSize() / ( 480 * numBlocks );
}
bool RawHandler::Open( const char *s )
{
unsigned char data[ 4 ];
assert( fd == -1 );
fd = open( s, O_RDONLY | O_NONBLOCK );
if ( fd < 0 )
return false;
if ( read( fd, data, 4 ) < 0 )
return false;
lseek( fd, 0, SEEK_SET );
numBlocks = ( ( data[ 3 ] & 0x80 ) == 0 ) ? 250 : 300;
filename = s;
return true;
}
int RawHandler::GetFrame( uint8_t *data, int frameNum )
{
assert( fd != -1 );
int size = 480 * numBlocks;
if ( frameNum < 0 )
return -1;
off_t offset = ( ( off_t ) frameNum * ( off_t ) size );
fail_if( lseek( fd, offset, SEEK_SET ) == ( off_t ) - 1 );
if ( read( fd, data, size ) > 0 )
return 0;
else
return -1;
}
/***************************************************************************/
AVIHandler::AVIHandler( int format ) : avi( NULL ), aviFormat( format ), isOpenDML( false ),
fccHandler( make_fourcc( "dvsd" ) ), channels( 2 ), isFullyInitialized( false ),
audioBuffer( NULL )
{
extension = ".avi";
for ( int c = 0; c < 4; c++ )
audioChannels[ c ] = NULL;
}
AVIHandler::~AVIHandler()
{
if ( audioBuffer != NULL )
{
delete audioBuffer;
audioBuffer = NULL;
}
for ( int c = 0; c < 4; c++ )
{
if ( audioChannels[ c ] != NULL )
{
delete audioChannels[ c ];
audioChannels[ c ] = NULL;
}
}
delete avi;
}
#if 0
void AVIHandler::SetSampleFrame( const Frame& sample )
{
Pack pack;
sample.GetAudioInfo( audioInfo );
sample.GetVideoInfo( videoInfo );
sample.GetAAUXPack( 0x50, pack );
dvinfo.dwDVAAuxSrc = *( DWORD* ) ( pack.data + 1 );
sample.GetAAUXPack( 0x51, pack );
dvinfo.dwDVAAuxCtl = *( DWORD* ) ( pack.data + 1 );
sample.GetAAUXPack( 0x52, pack );
dvinfo.dwDVAAuxSrc1 = *( DWORD* ) ( pack.data + 1 );
sample.GetAAUXPack( 0x53, pack );
dvinfo.dwDVAAuxCtl1 = *( DWORD* ) ( pack.data + 1 );
sample.GetVAUXPack( 0x60, pack );
dvinfo.dwDVVAuxSrc = *( DWORD* ) ( pack.data + 1 );
sample.GetVAUXPack( 0x61, pack );
dvinfo.dwDVVAuxCtl = *( DWORD* ) ( pack.data + 1 );
#ifdef WITH_LIBDV
if ( sample.decoder->std == e_dv_std_smpte_314m )
fccHandler = make_fourcc( "dv25" );
#endif
}
#endif
bool AVIHandler::FileIsOpen()
{
return avi != NULL;
}
bool AVIHandler::Create( const string& filename )
{
assert( avi == NULL );
switch ( aviFormat )
{
case AVI_DV1_FORMAT:
fail_null( avi = new AVI1File );
if ( avi->Create( filename.c_str() ) == false )
return false;
//avi->Init( videoInfo.isPAL ? AVI_PAL : AVI_NTSC, audioInfo.frequency, AVI_LARGE_INDEX );
break;
case AVI_DV2_FORMAT:
fail_null( avi = new AVI2File );
if ( avi->Create( filename.c_str() ) == false )
return false;
//if ( GetOpenDML() )
//avi->Init( videoInfo.isPAL ? AVI_PAL : AVI_NTSC, audioInfo.frequency,
//( AVI_SMALL_INDEX | AVI_LARGE_INDEX ) );
//else
//avi->Init( videoInfo.isPAL ? AVI_PAL : AVI_NTSC, audioInfo.frequency,
//( AVI_SMALL_INDEX ) );
break;
default:
assert( aviFormat == AVI_DV1_FORMAT || aviFormat == AVI_DV2_FORMAT );
}
avi->setDVINFO( dvinfo );
avi->setFccHandler( make_fourcc( "iavs" ), fccHandler );
avi->setFccHandler( make_fourcc( "vids" ), fccHandler );
this->filename = filename;
FileTracker::GetInstance().Add( filename.c_str() );
return ( avi != NULL );
}
#if 0
int AVIHandler::Write( const Frame& frame )
{
assert( avi != NULL );
try
{
return avi->WriteFrame( frame ) ? 0 : -1;
}
catch (...)
{
return -1;
}
}
#endif
int AVIHandler::Close()
{
if ( avi != NULL )
{
avi->WriteRIFF();
delete avi;
avi = NULL;
}
if ( audioBuffer != NULL )
{
delete audioBuffer;
audioBuffer = NULL;
}
for ( int c = 0; c < 4; c++ )
{
if ( audioChannels[ c ] != NULL )
{
delete audioChannels[ c ];
audioChannels[ c ] = NULL;
}
}
isFullyInitialized = false;
return 0;
}
off_t AVIHandler::GetFileSize()
{
return avi->GetFileSize();
}
int AVIHandler::GetTotalFrames()
{
return avi->GetTotalFrames();
}
bool AVIHandler::Open( const char *s )
{
assert( avi == NULL );
fail_null( avi = new AVI1File );
if ( avi->Open( s ) )
{
avi->ParseRIFF();
if ( ! (
avi->verifyStreamFormat( make_fourcc( "dvsd" ) ) ||
avi->verifyStreamFormat( make_fourcc( "DVSD" ) ) ||
avi->verifyStreamFormat( make_fourcc( "dvcs" ) ) ||
avi->verifyStreamFormat( make_fourcc( "DVCS" ) ) ||
avi->verifyStreamFormat( make_fourcc( "dvcp" ) ) ||
avi->verifyStreamFormat( make_fourcc( "DVCP" ) ) ||
avi->verifyStreamFormat( make_fourcc( "CDVC" ) ) ||
avi->verifyStreamFormat( make_fourcc( "cdvc" ) ) ||
avi->verifyStreamFormat( make_fourcc( "DV25" ) ) ||
avi->verifyStreamFormat( make_fourcc( "dv25" ) ) ) )
return false;
avi->ReadIndex();
if ( avi->verifyStream( make_fourcc( "auds" ) ) )
aviFormat = AVI_DV2_FORMAT;
else
aviFormat = AVI_DV1_FORMAT;
isOpenDML = avi->isOpenDML();
filename = s;
return true;
}
else
return false;
}
int AVIHandler::GetFrame( uint8_t *data, int frameNum )
{
int result = avi->GetDVFrame( data, frameNum );
#if 0
if ( result == 0 )
{
/* get the audio from the audio stream, if available */
if ( aviFormat == AVI_DV2_FORMAT )
{
WAVEFORMATEX wav;
if ( ! isFullyInitialized &&
avi->getStreamFormat( ( void* ) &wav, make_fourcc( "auds" ) ) )
{
if ( channels > 0 && channels < 5 )
{
// Allocate interleaved audio buffer
audioBuffer = new int16_t[ 2 * DV_AUDIO_MAX_SAMPLES * channels ];
// Allocate non-interleaved audio buffers
for ( int c = 0; c < channels; c++ )
audioChannels[ c ] = new int16_t[ 2 * DV_AUDIO_MAX_SAMPLES ];
// Get the audio parameters from AVI for subsequent calls to method
audioInfo.channels = wav.nChannels;
audioInfo.frequency = wav.nSamplesPerSec;
// Skip initialization on subsequent calls to method
isFullyInitialized = true;
cerr << ">>> using audio from separate AVI audio stream" << endl;
}
}
// Get the frame from AVI
int n = avi->getFrame( audioBuffer, frameNum, make_fourcc( "01wb" ) );
if ( n > 0 )
{
// Temporary pointer to audio scratch buffer
int16_t * s = audioBuffer;
// Determine samples in this frame
audioInfo.samples = n / audioInfo.channels / sizeof( int16_t );
// Convert interleaved audio into non-interleaved
for ( int n = 0; n < audioInfo.samples; ++n )
for ( int i = 0; i < audioInfo.channels; i++ )
audioChannels[ i ][ n ] = *s++;
// Write interleaved audio into frame
frame.EncodeAudio( audioInfo, audioChannels );
}
}
// Parse important metadata in DV bitstream
frame.ExtractHeader();
}
#endif
return result;
}
void AVIHandler::SetOpenDML( bool flag )
{
isOpenDML = flag;
}
bool AVIHandler::GetOpenDML() const
{
return isOpenDML;
}
/***************************************************************************/
#ifdef HAVE_LIBQUICKTIME
#ifndef HAVE_LIBDV
#define DV_AUDIO_MAX_SAMPLES 1944
#endif
// Missing fourcc's in libquicktime (allows compilation)
#ifndef QUICKTIME_DV_AVID
#define QUICKTIME_DV_AVID "AVdv"
#endif
#ifndef QUICKTIME_DV_AVID_A
#define QUICKTIME_DV_AVID_A "dvcp"
#endif
#ifndef QUICKTIME_DVCPRO
#define QUICKTIME_DVCPRO "dvpp"
#endif
QtHandler::QtHandler() : fd( NULL )
{
extension = ".mov";
Init();
}
QtHandler::~QtHandler()
{
Close();
}
void QtHandler::Init()
{
if ( fd != NULL )
Close();
fd = NULL;
samplingRate = 0;
samplesPerBuffer = 0;
channels = 2;
audioBuffer = NULL;
audioChannelBuffer = NULL;
isFullyInitialized = false;
}
bool QtHandler::FileIsOpen()
{
return fd != NULL;
}
bool QtHandler::Create( const string& filename )
{
Init();
if ( open( filename.c_str(), O_CREAT | O_TRUNC | O_RDWR | O_NONBLOCK, 0644 ) != -1 )
{
fd = quicktime_open( const_cast<char*>( filename.c_str() ), 0, 1 );
if ( fd != NULL )
FileTracker::GetInstance().Add( filename.c_str() );
}
else
return false;
this->filename = filename;
return true;
}
void QtHandler::AllocateAudioBuffers()
{
if ( channels > 0 && channels < 5 )
{
audioBufferSize = DV_AUDIO_MAX_SAMPLES * 2;
audioBuffer = new int16_t[ audioBufferSize * channels ];
audioChannelBuffer = new short int * [ channels ];
for ( int c = 0; c < channels; c++ )
audioChannelBuffer[ c ] = new short int[ audioBufferSize ];
isFullyInitialized = true;
}
}
inline void QtHandler::DeinterlaceStereo16( void* pInput, int iBytes,
void* pLOutput, void* pROutput )
{
short int * piSampleInput = ( short int* ) pInput;
short int* piSampleLOutput = ( short int* ) pLOutput;
short int* piSampleROutput = ( short int* ) pROutput;
while ( ( char* ) piSampleInput < ( ( char* ) pInput + iBytes ) )
{
*piSampleLOutput++ = *piSampleInput++;
*piSampleROutput++ = *piSampleInput++;
}
}
#if 0
int QtHandler::Write( const Frame& frame )
{
if ( ! isFullyInitialized )
{
AudioInfo audio;
if ( frame.GetAudioInfo( audio ) )
{
channels = 2;
quicktime_set_audio( fd, channels, audio.frequency, 16, QUICKTIME_TWOS );
}
else
{
channels = 0;
}
quicktime_set_video( fd, 1, 720, frame.IsPAL() ? 576 : 480,
frame.GetFrameRate(), QUICKTIME_DV );
AllocateAudioBuffers();
}
int result = quicktime_write_frame( fd, const_cast<unsigned char*>( frame.data ),
frame.GetFrameSize(), 0 );
if ( channels > 0 )
{
AudioInfo audio;
if ( frame.GetAudioInfo( audio ) && ( unsigned int ) audio.samples < audioBufferSize )
{
long bytesRead = frame.ExtractAudio( audioBuffer );
DeinterlaceStereo16( audioBuffer, bytesRead,
audioChannelBuffer[ 0 ],
audioChannelBuffer[ 1 ] );
quicktime_encode_audio( fd, audioChannelBuffer, NULL, audio.samples );
}
}
return result;
}
#endif
int QtHandler::Close()
{
if ( fd != NULL )
{
quicktime_close( fd );
fd = NULL;
}
if ( audioBuffer != NULL )
{
delete audioBuffer;
audioBuffer = NULL;
}
if ( audioChannelBuffer != NULL )
{
for ( int c = 0; c < channels; c++ )
delete audioChannelBuffer[ c ];
delete audioChannelBuffer;
audioChannelBuffer = NULL;
}
return 0;
}
off_t QtHandler::GetFileSize()
{
struct stat file_status;
stat( filename.c_str(), &file_status );
return file_status.st_size;
}
int QtHandler::GetTotalFrames()
{
return ( int ) quicktime_video_length( fd, 0 );
}
bool QtHandler::Open( const char *s )
{
Init();
fd = quicktime_open( ( char * ) s, 1, 0 );
if ( fd == NULL )
{
fprintf( stderr, "Error opening: %s\n", s );
return false;
}
if ( quicktime_has_video( fd ) <= 0 )
{
fprintf( stderr, "There must be at least one video track in the input file (%s).\n",
s );
Close();
return false;
}
char * fcc = quicktime_video_compressor( fd, 0 );
if ( strncmp( fcc, QUICKTIME_DV, 4 ) != 0 &&
strncmp( fcc, QUICKTIME_DV_AVID, 4 ) != 0 &&
strncmp( fcc, QUICKTIME_DV_AVID_A, 4 ) != 0 &&
strncmp( fcc, QUICKTIME_DVCPRO, 4 ) != 0 )
{
Close();
return false;
}
if ( quicktime_has_audio( fd ) )
channels = quicktime_track_channels( fd, 0 );
filename = s;
return true;
}
int QtHandler::GetFrame( uint8_t *data, int frameNum )
{
assert( fd != NULL );
quicktime_set_video_position( fd, frameNum, 0 );
quicktime_read_frame( fd, data, 0 );
#ifdef HAVE_LIBDV
if ( quicktime_has_audio( fd ) )
{
if ( ! isFullyInitialized )
AllocateAudioBuffers();
// Fetch the frequency of the audio track and calc number of samples needed
int frequency = quicktime_sample_rate( fd, 0 );
float fps = ( data[ 3 ] & 0x80 ) ? 25.0f : 29.97f;
int samples = mlt_sample_calculator( fps, frequency, frameNum );
int64_t seek = mlt_sample_calculator_to_now( fps, frequency, frameNum );
// Obtain a dv encoder and initialise it with minimal info
dv_encoder_t *encoder = dv_encoder_new( 0, 0, 0 );
encoder->isPAL = ( data[ 3 ] & 0x80 );
encoder->samples_this_frame = samples;
// Seek to the calculated position and decode
quicktime_set_audio_position( fd, seek, 0 );
lqt_decode_audio( fd, audioChannelBuffer, NULL, (long) samples );
// Encode the audio on the frame and done
dv_encode_full_audio( encoder, audioChannelBuffer, channels, frequency, data );
dv_encoder_free( encoder );
}
#endif
return 0;
}
#endif