Skip to content
Permalink
master
Switch branches/tags
Go to file
 
 
Cannot retrieve contributors at this time
2299 lines (1804 sloc) 70.2 KB
/*******************************************************************************
* *
* SDL_ffmpeg is a library for basic multimedia functionality. *
* SDL_ffmpeg is based on ffmpeg. *
* *
* Copyright (C) 2007 Arjan Houben *
* *
* SDL_ffmpeg is free software: you can redistribute it and/or modify *
* it under the terms of the GNU Lesser General Public License as published *
* by the Free Software Foundation, either version 3 of the License, or 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 Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public License *
* along with this program. If not, see <http://www.gnu.org/licenses/>. *
* *
*******************************************************************************/
/**
@mainpage
@version 1.3.2
@author Arjan Houben
SDL_ffmpeg is designed with ease of use in mind.
Even the beginning programmer should be able to use this library
so he or she can use multimedia in his/her program.
**/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <SDL.h>
#include <SDL_thread.h>
#ifdef __cplusplus
extern "C"
{
#endif
#include "libavformat/avformat.h"
#include "libavutil/mathematics.h"
#include "libswscale/swscale.h"
#ifdef __cplusplus
}
#endif
// FFmpeg compatibility
#ifndef AVCODEC_MAX_AUDIO_FRAME_SIZE
#define AVCODEC_MAX_AUDIO_FRAME_SIZE 192000
#endif
#include "SDL_ffmpeg.h"
#ifdef MSVC
#define snprintf( buf, count, format, ... ) _snprintf_s( buf, 512, count, format, __VA_ARGS__ )
#ifndef INT64_C
#define INT64_C(i) i
#endif
#endif
#ifdef __cplusplus
extern "C"
{
#endif
// Use audio conversion from Movie.cpp
extern int convert_audio(int in_samples, int in_channels, int in_stride,
enum AVSampleFormat in_fmt, const uint8_t *in_buf,
int out_samples, int out_channels, int out_stride,
enum AVSampleFormat out_fmt, uint8_t *out_buf);
#ifdef __cplusplus
}
#endif
/**
\cond
*/
/**
* Provide a fast way to get the correct context.
* \returns The context matching the input values.
*/
struct SwsContext* getContext( SDL_ffmpegConversionContext **context, int inWidth, int inHeight, enum AVPixelFormat inFormat, int outWidth, int outHeight, enum AVPixelFormat outFormat )
{
SDL_ffmpegConversionContext *ctx = *context;
if ( ctx )
{
/* check for a matching context */
while ( ctx )
{
if ( ctx->inWidth == inWidth &&
ctx->inHeight == inHeight &&
ctx->inFormat == inFormat &&
ctx->outWidth == outWidth &&
ctx->outHeight == outHeight &&
ctx->outFormat == outFormat )
{
return ctx->context;
}
ctx = ctx->next;
}
ctx = *context;
/* find the last context */
while ( ctx && ctx->next ) ctx = ctx->next;
/* allocate a new context */
ctx->next = ( struct SDL_ffmpegConversionContext* ) malloc( sizeof( SDL_ffmpegConversionContext ) );
ctx = ctx->next;
}
else
{
ctx = *context = ( struct SDL_ffmpegConversionContext* ) malloc( sizeof( SDL_ffmpegConversionContext ) );
// TODO handle the case where ctx is still null due to malloc failure
}
/* fill context with correct information */
ctx->context = sws_getContext( inWidth, inHeight, inFormat,
outWidth, outHeight, outFormat,
SWS_BILINEAR,
0,
0,
0 );
ctx->inWidth = inWidth;
ctx->inHeight = inHeight;
ctx->inFormat = inFormat;
ctx->outWidth = outWidth;
ctx->outHeight = outHeight;
ctx->outFormat = outFormat;
ctx->next = 0;
return ctx->context;
}
uint32_t SDL_ffmpegInitWasCalled = 0;
/* error handling */
char SDL_ffmpegErrorMessage[ 512 ];
void SDL_ffmpegSetError( const char *error );
/* packet handling */
int SDL_ffmpegGetPacket( SDL_ffmpegFile* );
SDL_ffmpegPacket* SDL_ffmpegGetAudioPacket( SDL_ffmpegFile* );
SDL_ffmpegPacket* SDL_ffmpegGetVideoPacket( SDL_ffmpegFile* );
/* frame handling */
int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile*, AVPacket*, SDL_ffmpegAudioFrame* );
int SDL_ffmpegDecodeVideoFrame( SDL_ffmpegFile*, AVPacket*, SDL_ffmpegVideoFrame* );
const SDL_ffmpegCodec SDL_ffmpegCodecAUTO =
{
-1,
720, 576,
1, 25,
6000000,
-1, -1,
-1,
2, 48000,
192000,
-1, -1
};
const SDL_ffmpegCodec SDL_ffmpegCodecPALDVD =
{
AV_CODEC_ID_MPEG2VIDEO,
720, 576,
1, 25,
6000000,
-1, -1,
AV_CODEC_ID_MP2,
2, 48000,
192000,
-1, -1
};
const SDL_ffmpegCodec SDL_ffmpegCodecPALDV =
{
AV_CODEC_ID_DVVIDEO,
720, 576,
1, 25,
6553600,
-1, -1,
AV_CODEC_ID_DVAUDIO,
2, 48000,
256000,
-1, -1
};
SDL_ffmpegFile* SDL_ffmpegCreateFile()
{
/* create SDL_ffmpegFile pointer */
SDL_ffmpegFile *file = ( SDL_ffmpegFile* )malloc( sizeof( SDL_ffmpegFile ) );
if ( !file )
{
SDL_ffmpegSetError( "could not allocate SDL_ffmpegFile" );
return 0;
}
memset( file, 0, sizeof( SDL_ffmpegFile ) );
file->streamMutex = SDL_CreateMutex();
return file;
}
/**
\endcond
*/
/** \brief Initializes the SDL_ffmpeg library
This is done automatically when using SDL_ffmpegOpen or
SDL_ffmpegCreateFile. This means that it is usualy unnescecairy
to explicitly call this function
*/
void SDL_ffmpegInit()
{
/* register all codecs */
if ( !SDL_ffmpegInitWasCalled )
{
SDL_ffmpegInitWasCalled = 1;
avcodec_register_all();
av_register_all();
}
}
/** \brief Use this to free an SDL_ffmpegFile.
This function stops the decoding thread if needed
and flushes the buffers before releasing the memory.
\param file SDL_ffmpegFile which needs to be removed
*/
void SDL_ffmpegFree( SDL_ffmpegFile *file )
{
if ( !file ) return;
SDL_ffmpegFlush( file );
/* only write trailer when handling output streams */
if ( file->type == SDL_ffmpegOutputStream )
{
av_write_trailer( file->_ffmpeg );
}
SDL_ffmpegStream *s = file->vs;
while ( s )
{
SDL_ffmpegStream *old = s;
s = s->next;
SDL_DestroyMutex( old->mutex );
while ( old->buffer )
{
SDL_ffmpegPacket *pack = old->buffer;
old->buffer = old->buffer->next;
av_free_packet( pack->data );
av_free( pack->data );
free( pack );
}
while ( old->conversionContext )
{
SDL_ffmpegConversionContext *ctx = old->conversionContext;
old->conversionContext = old->conversionContext->next;
sws_freeContext( ctx->context );
free( ctx );
}
av_free( old->decodeFrame );
if ( old->_ffmpeg ) avcodec_close( old->_ffmpeg->codec );
free( old );
}
s = file->as;
while ( s )
{
SDL_ffmpegStream *old = s;
s = s->next;
SDL_DestroyMutex( old->mutex );
while ( old->buffer )
{
SDL_ffmpegPacket *pack = old->buffer;
old->buffer = old->buffer->next;
av_free_packet( pack->data );
av_free( pack->data );
free( pack );
}
av_free( old->sampleBuffer );
if ( old->_ffmpeg ) avcodec_close( old->_ffmpeg->codec );
free( old );
}
if ( file->_ffmpeg )
{
if ( file->type == SDL_ffmpegInputStream )
{
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(53,17,0)
av_close_input_file( file->_ffmpeg );
#else
avformat_close_input( &file->_ffmpeg );
#endif
}
else if ( file->type == SDL_ffmpegOutputStream )
{
avio_close( file->_ffmpeg->pb );
av_free( file->_ffmpeg );
}
}
SDL_DestroyMutex( file->streamMutex );
free( file );
}
/** \brief Use this to free an SDL_ffmpegAudioFrame.
This releases all buffers which where allocated in SDL_ffmpegCreateAudioFrame
\param frame SDL_ffmpegAudioFrame which needs to be deleted
*/
void SDL_ffmpegFreeAudioFrame( SDL_ffmpegAudioFrame* frame )
{
av_free( frame->buffer );
free( frame );
}
/** \brief Use this to free an SDL_ffmpegVideoFrame.
This releases all buffers which where allocated in SDL_ffmpegCreateVideoFrame
\param frame SDL_ffmpegVideoFrame which needs to be deleted
*/
void SDL_ffmpegFreeVideoFrame( SDL_ffmpegVideoFrame* frame )
{
if ( !frame ) return;
if ( frame->surface ) SDL_FreeSurface( frame->surface );
// if ( frame->overlay ) SDL_FreeYUVOverlay( frame->overlay );
free( frame );
}
/** \brief Use this to open the multimedia file of your choice.
This function is used to open a multimedia file.
When the file could be opened, but no decodable streams where detected
this function still returns a pointer to a valid SDL_ffmpegFile.
\param filename string containing the location of the file
\returns a pointer to a SDL_ffmpegFile structure, or NULL if a file could not be opened
*/
SDL_ffmpegFile* SDL_ffmpegOpen( const char* filename )
{
SDL_ffmpegInit();
/* open new ffmpegFile */
SDL_ffmpegFile *file = SDL_ffmpegCreateFile();
if ( !file ) return 0;
/* information about format is stored in file->_ffmpeg */
file->type = SDL_ffmpegInputStream;
/* open the file */
if ( avformat_open_input(&file->_ffmpeg, filename, NULL, NULL) != 0 )
{
char c[512];
snprintf( c, 512, "could not open \"%s\"", filename );
SDL_ffmpegSetError( c );
free( file );
return 0;
}
/* retrieve format information */
if ( avformat_find_stream_info( file->_ffmpeg, NULL ) < 0 )
{
char c[512];
snprintf( c, 512, "could not retrieve file info for \"%s\"", filename );
SDL_ffmpegSetError( c );
free( file );
return 0;
}
/* iterate through all the streams and store audio/video streams */
for ( uint32_t i = 0; i < file->_ffmpeg->nb_streams; i++ )
{
/* disable all streams by default */
file->_ffmpeg->streams[i]->discard = AVDISCARD_ALL;
if ( file->_ffmpeg->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO )
{
/* if this is a packet of the correct type we create a new stream */
SDL_ffmpegStream* stream = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );
if ( stream )
{
/* we set our stream to zero */
memset( stream, 0, sizeof( SDL_ffmpegStream ) );
/* save unique streamid */
stream->id = i;
/* _ffmpeg holds data about streamcodec */
stream->_ffmpeg = file->_ffmpeg->streams[i];
/* get the correct decoder for this stream */
AVCodec *codec = avcodec_find_decoder( stream->_ffmpeg->codec->codec_id );
if ( !codec )
{
free( stream );
SDL_ffmpegSetError( "could not find video codec" );
}
else if ( avcodec_open2( file->_ffmpeg->streams[i]->codec, codec, NULL ) < 0 )
{
free( stream );
SDL_ffmpegSetError( "could not open video codec" );
}
else
{
stream->mutex = SDL_CreateMutex();
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,0)
stream->decodeFrame = avcodec_alloc_frame();
#else
stream->decodeFrame = av_frame_alloc();
#endif
SDL_ffmpegStream **s = &file->vs;
while ( *s )
{
*s = ( *s )->next;
}
*s = stream;
file->videoStreams++;
}
}
}
else if ( file->_ffmpeg->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO )
{
/* if this is a packet of the correct type we create a new stream */
SDL_ffmpegStream* stream = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );
if ( stream )
{
/* we set our stream to zero */
memset( stream, 0, sizeof( SDL_ffmpegStream ) );
/* save unique streamid */
stream->id = i;
/* _ffmpeg holds data about streamcodec */
stream->_ffmpeg = file->_ffmpeg->streams[i];
/* get the correct decoder for this stream */
AVCodec *codec = avcodec_find_decoder( file->_ffmpeg->streams[i]->codec->codec_id );
if ( !codec )
{
free( stream );
SDL_ffmpegSetError( "could not find audio codec" );
}
else if ( avcodec_open2( file->_ffmpeg->streams[i]->codec, codec, NULL ) < 0 )
{
free( stream );
SDL_ffmpegSetError( "could not open audio codec" );
}
else
{
stream->mutex = SDL_CreateMutex();
stream->sampleBuffer = ( int8_t* )av_malloc( AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t ) );
stream->sampleBufferSize = 0;
stream->sampleBufferOffset = 0;
stream->sampleBufferTime = AV_NOPTS_VALUE;
SDL_ffmpegStream **s = &file->as;
while ( *s )
{
*s = ( *s )->next;
}
*s = stream;
file->audioStreams++;
}
}
}
}
return file;
}
#ifdef SDL_FF_WRITE
/** \brief Use this to create the multimedia file of your choice.
This function is used to create a multimedia file.
\param filename string containing the location to which the data will be written
\returns a pointer to a SDL_ffmpegFile structure, or NULL if a file could not be opened
*/
SDL_ffmpegFile* SDL_ffmpegCreate( const char* filename )
{
SDL_ffmpegInit();
SDL_ffmpegFile *file = SDL_ffmpegCreateFile();
file->_ffmpeg = avformat_alloc_context();
/* guess output format based on filename */
#if ( LIBAVFORMAT_VERSION_MAJOR <= 52 && LIBAVFORMAT_VERSION_MINOR <= 45 )
file->_ffmpeg->oformat = guess_format( 0, filename, 0 );
#else
file->_ffmpeg->oformat = av_guess_format( 0, filename, 0 );
#endif
if ( !file->_ffmpeg->oformat )
{
#if ( LIBAVFORMAT_VERSION_MAJOR <= 52 && LIBAVFORMAT_VERSION_MINOR <= 45 )
file->_ffmpeg->oformat = guess_format( "dvd", 0, 0 );
#else
file->_ffmpeg->oformat = av_guess_format( "dvd", 0, 0 );
#endif
}
/* preload as shown in ffmpeg.c */
file->_ffmpeg->preload = ( int )( 0.5 * AV_TIME_BASE );
/* max delay as shown in ffmpeg.c */
file->_ffmpeg->max_delay = ( int )( 0.7 * AV_TIME_BASE );
/* open the output file, if needed */
if ( url_fopen( &file->_ffmpeg->pb, filename, AVIO_FLAG_WRITE ) < 0 )
{
char c[512];
snprintf( c, 512, "could not open \"%s\"", filename );
SDL_ffmpegSetError( c );
SDL_ffmpegFree( file );
return 0;
}
file->type = SDL_ffmpegOutputStream;
return file;
}
#endif
#ifdef SDL_FF_WRITE
/** \brief Use this to add a SDL_ffmpegVideoFrame to file
By adding frames to file, a video stream is build. If an audio stream
is present, syncing of both streams needs to be done by user.
\param file SDL_ffmpegFile to which a frame needs to be added.
\param frame SDL_ffmpegVideoFrame which will be added to the stream.
\returns 0 if frame was added, non-zero if an error occured.
*/
int SDL_ffmpegAddVideoFrame( SDL_ffmpegFile *file, SDL_Surface *frame )
{
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
if ( !file->videoStream || !frame || !frame->format )
{
SDL_UnlockMutex( file->streamMutex );
return -1;
}
int pitch [] =
{
frame->pitch,
0
};
const uint8_t *const data [] =
{
(uint8_t *)frame->pixels,
0
};
switch ( frame->format->BitsPerPixel )
{
case 24:
sws_scale( getContext( &file->videoStream->conversionContext,
frame->w, frame->h, AV_PIX_FMT_RGB24,
file->videoStream->_ffmpeg->codec->width,
file->videoStream->_ffmpeg->codec->height,
file->videoStream->_ffmpeg->codec->pix_fmt ),
data,
pitch,
0,
frame->h,
file->videoStream->encodeFrame->data,
file->videoStream->encodeFrame->linesize );
break;
case 32:
sws_scale( getContext( &file->videoStream->conversionContext,
frame->w, frame->h, AV_PIX_FMT_BGR32,
file->videoStream->_ffmpeg->codec->width,
file->videoStream->_ffmpeg->codec->height,
file->videoStream->_ffmpeg->codec->pix_fmt ),
data,
pitch,
0,
frame->h,
file->videoStream->encodeFrame->data,
file->videoStream->encodeFrame->linesize );
break;
default:
break;
}
/* PAL = upper field first
file->videoStream->encodeFrame->top_field_first = 1;
*/
int out_size = avcodec_encode_video( file->videoStream->_ffmpeg->codec, file->videoStream->encodeFrameBuffer, file->videoStream->encodeFrameBufferSize, file->videoStream->encodeFrame );
/* if zero size, it means the image was buffered */
if ( out_size > 0 )
{
AVPacket pkt;
av_init_packet( &pkt );
/* set correct stream index for this packet */
pkt.stream_index = file->videoStream->_ffmpeg->index;
/* set keyframe flag if needed */
if ( file->videoStream->_ffmpeg->codec->coded_frame->key_frame ) pkt.flags |= AV_PKT_FLAG_KEY;
/* write encoded data into packet */
pkt.data = file->videoStream->encodeFrameBuffer;
/* set the correct size of this packet */
pkt.size = out_size;
/* set the correct duration of this packet */
pkt.duration = AV_TIME_BASE / file->videoStream->_ffmpeg->time_base.den;
/* if needed info is available, write pts for this packet */
if ( file->videoStream->_ffmpeg->codec->coded_frame->pts != AV_NOPTS_VALUE )
{
pkt.pts = av_rescale_q( file->videoStream->_ffmpeg->codec->coded_frame->pts, file->videoStream->_ffmpeg->codec->time_base, file->videoStream->_ffmpeg->time_base );
}
av_write_frame( file->_ffmpeg, &pkt );
av_free_packet( &pkt );
file->videoStream->frameCount++;
}
SDL_UnlockMutex( file->streamMutex );
return 0;
}
#endif
#ifdef SDL_FF_WRITE
/** \brief Use this to add a SDL_ffmpegAudioFrame to file
By adding frames to file, an audio stream is build. If a video stream
is present, syncing of both streams needs to be done by user.
\param file SDL_ffmpegFile to which a frame needs to be added.
\param frame SDL_ffmpegAudioFrame which will be added to the stream.
\returns 0 if frame was added, non-zero if an error occured.
*/
int SDL_ffmpegAddAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame )
{
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
if ( !file || !file->audioStream || !frame )
{
SDL_UnlockMutex( file->streamMutex );
return -1;
}
AVPacket pkt;
/* initialize a packet to write */
av_init_packet( &pkt );
/* set correct stream index for this packet */
pkt.stream_index = file->audioStream->_ffmpeg->index;
/* set keyframe flag if needed */
pkt.flags |= AV_PKT_FLAG_KEY;
/* set the correct size of this packet */
pkt.size = avcodec_encode_audio( file->audioStream->_ffmpeg->codec, ( uint8_t* )file->audioStream->sampleBuffer, file->audioStream->sampleBufferSize, ( int16_t* )frame->buffer );
/* write encoded data into packet */
pkt.data = ( uint8_t* )file->audioStream->sampleBuffer;
/* if needed info is available, write pts for this packet */
if ( file->audioStream->_ffmpeg->codec->coded_frame->pts != AV_NOPTS_VALUE )
{
pkt.pts = av_rescale_q( file->audioStream->_ffmpeg->codec->coded_frame->pts, file->audioStream->_ffmpeg->codec->time_base, file->audioStream->_ffmpeg->time_base );
}
/* write packet to stream */
av_write_frame( file->_ffmpeg, &pkt );
av_free_packet( &pkt );
file->audioStream->frameCount++;
SDL_UnlockMutex( file->streamMutex );
return 0;
}
#endif
/** \brief Use this to create a SDL_ffmpegAudioFrame
With this frame, you can receive audio data from the stream using
SDL_ffmpegGetAudioFrame.
\param file SDL_ffmpegFile for which a frame needs to be created
\param bytes When current active audio stream is an input stream, this holds
the size of the buffer which will be allocated. In case of an
output stream, this value is ignored.
\returns Pointer to SDL_ffmpegAudioFrame, or NULL if no frame could be created
*/
SDL_ffmpegAudioFrame* SDL_ffmpegCreateAudioFrame( SDL_ffmpegFile *file, uint32_t bytes )
{
if (!file) {
return 0;
}
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
if (!file->audioStream || ( !bytes && file->type == SDL_ffmpegInputStream ) )
{
SDL_UnlockMutex( file->streamMutex );
return 0;
}
/* allocate new frame */
SDL_ffmpegAudioFrame *frame = ( SDL_ffmpegAudioFrame* )malloc( sizeof( SDL_ffmpegAudioFrame ) );
memset( frame, 0, sizeof( SDL_ffmpegAudioFrame ) );
if ( file->type == SDL_ffmpegOutputStream )
{
bytes = file->audioStream->encodeAudioInputSize * 2 * file->audioStream->_ffmpeg->codec->channels;
}
SDL_UnlockMutex( file->streamMutex );
/* set capacity of new frame */
frame->capacity = bytes;
/* allocate buffer */
frame->buffer = ( uint8_t* )av_malloc( bytes );
/* initialize a non-valid timestamp */
frame->pts = AV_NOPTS_VALUE;
return frame;
}
/** \brief Use this to create a SDL_ffmpegVideoFrame
In order to receive video data, either SDL_ffmpegVideoFrame.surface or
SDL_ffmpegVideoFrame.overlay need to be set by user.
\returns Pointer to SDL_ffmpegVideoFrame, or NULL if no frame could be created
*/
SDL_ffmpegVideoFrame* SDL_ffmpegCreateVideoFrame()
{
SDL_ffmpegVideoFrame *frame = ( SDL_ffmpegVideoFrame* )malloc( sizeof( SDL_ffmpegVideoFrame ) );
// TODO return failure if frame could not be allocated, unsure how that should look
memset( frame, 0, sizeof( SDL_ffmpegVideoFrame ) );
return frame;
}
/** \brief Use this to get new video data from file.
Using this function, you can retreive video data from file. This data
gets written to frame.
\param file SDL_ffmpegFile from which the data is required
\param frame SDL_ffmpegVideoFrame to which the data will be written
\returns non-zero when a frame was retreived, zero otherwise
*/
int SDL_ffmpegGetVideoFrame( SDL_ffmpegFile* file, SDL_ffmpegVideoFrame *frame )
{
if (!file) {
return 0;
}
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
if ( !frame || !file || !file->videoStream )
{
SDL_UnlockMutex( file->streamMutex );
return 0;
}
SDL_LockMutex( file->videoStream->mutex );
/* assume current frame is empty */
frame->ready = 0;
frame->last = 0;
/* get new packet */
SDL_ffmpegPacket *pack = SDL_ffmpegGetVideoPacket( file );
while ( !pack && !frame->last )
{
pack = SDL_ffmpegGetVideoPacket( file );
frame->last = SDL_ffmpegGetPacket( file );
}
while ( pack && !frame->ready )
{
/* when a frame is received, frame->ready will be set */
SDL_ffmpegDecodeVideoFrame( file, pack->data, frame );
/* destroy used packet */
av_free_packet( pack->data );
av_free( pack->data );
free( pack );
pack = SDL_ffmpegGetVideoPacket( file );
while ( !pack && !frame->last )
{
pack = SDL_ffmpegGetVideoPacket( file );
frame->last = SDL_ffmpegGetPacket( file );
}
}
/* pack retreived, but was not used, push it back in the buffer */
if ( pack )
{
/* take current buffer as next pointer */
pack->next = file->videoStream->buffer;
/* store pack as current buffer */
file->videoStream->buffer = pack;
}
else if ( !frame->ready && frame->last )
{
/* check if there is still a frame in the buffer */
SDL_ffmpegDecodeVideoFrame( file, 0, frame );
}
SDL_UnlockMutex( file->videoStream->mutex );
SDL_UnlockMutex( file->streamMutex );
return frame->ready;
}
/** \brief Get the desired audio stream from file.
This returns a pointer to the requested stream. With this stream pointer you can
get information about the stream, like language, samplerate, size etc.
Based on this information you can choose the stream you want to use.
\param file SDL_ffmpegFile from which the information is required
\param audioID is the stream you whish to select.
\returns Pointer to SDL_ffmpegStream, or NULL if selected stream could not be found
*/
SDL_ffmpegStream* SDL_ffmpegGetAudioStream( SDL_ffmpegFile *file, uint32_t audioID )
{
/* check if we have any audiostreams */
if ( !file || !file->audioStreams ) return 0;
SDL_ffmpegStream *s = file->as;
/* return audiostream linked to audioID */
for ( uint32_t i = 0; i < audioID && s; i++ ) s = s->next;
return s;
}
/** \brief Select an audio stream from file.
Use this function to select an audio stream for decoding.
Using SDL_ffmpegGetAudioStream you can get information about the streams.
Based on that you can chose the stream you want.
\param file SDL_ffmpegFile on which an action is required
\param audioID is the stream you whish to select. negative values de-select any audio stream.
\returns -1 on error, otherwise 0
*/
int SDL_ffmpegSelectAudioStream( SDL_ffmpegFile* file, int audioID )
{
if ( !file ) return -1;
/* when changing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
/* check if we have any audiostreams and if the requested ID is available */
if ( !file->audioStreams || audioID >= ( int )file->audioStreams )
{
SDL_UnlockMutex( file->streamMutex );
SDL_ffmpegSetError( "requested audio stream ID is not available in file" );
return -1;
}
/* set all audio streams to discard */
SDL_ffmpegStream *stream = file->as;
/* discard by default */
while ( stream && stream->_ffmpeg )
{
stream->_ffmpeg->discard = AVDISCARD_ALL;
stream = stream->next;
}
if ( audioID < 0 )
{
/* reset audiostream */
file->audioStream = 0;
}
else
{
/* set current audiostream to stream linked to audioID */
file->audioStream = file->as;
for ( int i = 0; i < audioID && file->audioStream; i++ ) file->audioStream = file->audioStream->next;
/* active stream need not be discarded */
if (file->audioStream && file->audioStream->_ffmpeg)
file->audioStream->_ffmpeg->discard = AVDISCARD_DEFAULT;
}
SDL_UnlockMutex( file->streamMutex );
return 0;
}
/** \brief Get the desired video stream from file.
This returns a pointer to the requested stream. With this stream pointer you can
get information about the stream, like language, samplerate, size etc.
Based on this information you can choose the stream you want to use.
\param file SDL_ffmpegFile from which the information is required
\param videoID is the stream you whish to select.
\returns Pointer to SDL_ffmpegStream, or NULL if selected stream could not be found
*/
SDL_ffmpegStream* SDL_ffmpegGetVideoStream( SDL_ffmpegFile *file, uint32_t videoID )
{
/* check if we have any audiostreams */
if ( !file || !file->videoStreams ) return 0;
/* check if the requested id is possible */
if ( videoID >= file->videoStreams ) return 0;
SDL_ffmpegStream *s = file->vs;
/* return audiostream linked to audioID */
for ( uint32_t i = 0; i < videoID && s; i++ ) s = s->next;
return s;
}
/** \brief Select a video stream from file.
Use this function to select a video stream for decoding.
Using SDL_ffmpegGetVideoStream you can get information about the streams.
Based on that you can chose the stream you want.
\param file SDL_ffmpegFile on which an action is required
\param videoID is the stream you whish to select.
\returns -1 on error, otherwise 0
*/
int SDL_ffmpegSelectVideoStream( SDL_ffmpegFile* file, int videoID )
{
if ( !file ) return -1;
/* when changing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
/* check if we have any videostreams */
if ( videoID >= ( int )file->videoStreams )
{
SDL_UnlockMutex( file->streamMutex );
SDL_ffmpegSetError( "requested video stream ID is not available in file" );
return -1;
}
/* set all video streams to discard */
SDL_ffmpegStream *stream = file->vs;
/* discard by default */
while ( stream && stream->_ffmpeg )
{
stream->_ffmpeg->discard = AVDISCARD_ALL;
stream = stream->next;
}
if ( videoID < 0 )
{
/* reset videostream */
file->videoStream = 0;
}
else
{
/* set current videostream to stream linked to videoID */
file->videoStream = file->vs;
/* keep searching for correct videostream */
for ( int i = 0; i < videoID && file->videoStream; i++ ) file->videoStream = file->videoStream->next;
/* active stream need not be discarded */
if (file->videoStream && file->videoStream->_ffmpeg)
file->videoStream->_ffmpeg->discard = AVDISCARD_DEFAULT;
}
SDL_UnlockMutex( file->streamMutex );
return 0;
}
/** \brief Seek to a certain point in file.
Tries to seek to specified point in file.
\param file SDL_ffmpegFile on which an action is required
\param timestamp is represented in milliseconds.
\returns -1 on error, otherwise 0
*/
int SDL_ffmpegSeek( SDL_ffmpegFile* file, uint64_t timestamp )
{
if ( !file ) return -1;
if ( SDL_ffmpegDuration( file ) < timestamp )
{
SDL_ffmpegSetError( "can not seek past end of file" );
return -1;
}
/* convert milliseconds to AV_TIME_BASE units */
uint64_t seekPos = timestamp * ( AV_TIME_BASE / 1000 );
/* AVSEEK_FLAG_BACKWARD means we jump to the first keyframe before seekPos */
av_seek_frame( file->_ffmpeg, -1, seekPos, AVSEEK_FLAG_BACKWARD );
/* set minimal timestamp to decode */
file->minimalTimestamp = timestamp;
/* flush buffers */
SDL_ffmpegFlush( file );
return 0;
}
/** \brief Seek to a relative point in file.
Tries to seek to new location, based on current location in file.
\param file SDL_ffmpegFile on which an action is required
\param timestamp is represented in milliseconds.
\returns -1 on error, otherwise 0
*/
int SDL_ffmpegSeekRelative( SDL_ffmpegFile *file, int64_t timestamp )
{
/* same thing as normal seek, just take into account the current position */
return SDL_ffmpegSeek( file, SDL_ffmpegGetPosition( file ) + timestamp );
}
/**
\cond
*/
int SDL_ffmpegFlush( SDL_ffmpegFile *file )
{
/* check for file and permission to flush buffers */
if ( !file ) return -1;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
/* if we have a valid audio stream, we flush it */
if ( file->audioStream )
{
SDL_LockMutex( file->audioStream->mutex );
SDL_ffmpegPacket *pack = file->audioStream->buffer;
while ( pack )
{
SDL_ffmpegPacket *old = pack;
pack = pack->next;
av_free_packet( old->data );
av_free( old->data );
free( old );
}
file->audioStream->buffer = 0;
/* flush internal ffmpeg buffers */
if ( file->audioStream->_ffmpeg )
{
avcodec_flush_buffers( file->audioStream->_ffmpeg->codec );
}
SDL_UnlockMutex( file->audioStream->mutex );
}
/* if we have a valid video stream, we flush some more */
if ( file->videoStream )
{
SDL_LockMutex( file->videoStream->mutex );
SDL_ffmpegPacket *pack = file->videoStream->buffer;
while ( pack )
{
SDL_ffmpegPacket *old = pack;
pack = pack->next;
av_free_packet( old->data );
av_free( old->data );
free( old );
}
file->videoStream->buffer = 0;
/* flush internal ffmpeg buffers */
if ( file->videoStream->_ffmpeg ) avcodec_flush_buffers( file->videoStream->_ffmpeg->codec );
SDL_UnlockMutex( file->videoStream->mutex );
}
SDL_UnlockMutex( file->streamMutex );
return 0;
}
/**
\endcond
*/
/** \brief Use this to get a pointer to a SDL_ffmpegAudioFrame.
If you receive a frame, it is valid until you receive a new frame, or
until the file is freed, using SDL_ffmpegFree( SDL_ffmpegFile* ).
I you use data from the frame, you should adjust the size member by
the amount of data used in bytes. This is needed so that SDL_ffmpeg can
calculate the next frame.
\param file SDL_ffmpegFile from which the information is required
\param frame The frame to which the data will be decoded.
\returns Pointer to SDL_ffmpegAudioFrame, or NULL if no frame was available.
*/
int SDL_ffmpegGetAudioFrame( SDL_ffmpegFile *file, SDL_ffmpegAudioFrame *frame )
{
if ( !file || !frame ) return -1;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
if ( !file->audioStream )
{
SDL_UnlockMutex( file->streamMutex );
SDL_ffmpegSetError( "no valid audio stream selected" );
return 0;
}
/* lock audio buffer */
SDL_LockMutex( file->audioStream->mutex );
/* reset frame end pointer and size */
frame->last = 0;
frame->size = 0;
/* get new packet */
SDL_ffmpegPacket *pack = SDL_ffmpegGetAudioPacket( file );
while ( !pack && !frame->last )
{
pack = SDL_ffmpegGetAudioPacket( file );
frame->last = SDL_ffmpegGetPacket( file );
}
/* SDL_ffmpegDecodeAudioFrame will return true if data from pack was used
frame will be updated with the new data */
while ( pack && SDL_ffmpegDecodeAudioFrame( file, pack->data, frame ) )
{
/* destroy used packet */
av_free_packet( pack->data );
av_free( pack->data );
free( pack );
pack = 0;
/* check if new packet is required */
if ( frame->size < frame->capacity )
{
/* try to get a new packet */
pack = SDL_ffmpegGetAudioPacket( file );
while ( !pack && !frame->last )
{
pack = SDL_ffmpegGetAudioPacket( file );
frame->last = SDL_ffmpegGetPacket( file );
}
}
}
/* pack retreived, but was not used, push it back in the buffer */
if ( pack )
{
/* take current buffer as next pointer */
pack->next = file->audioStream->buffer;
/* store pack as current buffer */
file->audioStream->buffer = pack;
}
/* unlock audio buffer */
SDL_UnlockMutex( file->audioStream->mutex );
SDL_UnlockMutex( file->streamMutex );
return ( frame->size == frame->capacity );
}
/** \brief Returns the current position of the file in milliseconds.
\param file SDL_ffmpegFile from which the information is required
\returns -1 on error, otherwise the length of the file in milliseconds
*/
int64_t SDL_ffmpegGetPosition( SDL_ffmpegFile *file )
{
if ( !file ) return -1;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
int64_t pos = 0;
if ( file->audioStream )
{
pos = file->audioStream->lastTimeStamp;
}
if ( file->videoStream && file->videoStream->lastTimeStamp > pos )
{
pos = file->videoStream->lastTimeStamp;
}
SDL_UnlockMutex( file->streamMutex );
/* return the current playposition of our file */
return pos;
}
/** \brief Returns the frame rate of the stream as a fraction.
This retreives the frame rate of the supplied stream.
For example, a framerate of 25 frames per second will have a nominator
of 1, and a denominator of 25.
\param stream SDL_ffmpegStream from which the information is required.
\param nominator Nominator part of the fraction. Can be 0 if exact value of
nominator is not required.
\param denominator Denominator part of the fraction. Can be 0 if exact value
of denominator is not required.
\returns The result of nominator / denominator as floating point value.
*/
float SDL_ffmpegGetFrameRate( SDL_ffmpegStream *stream, int *nominator, int *denominator )
{
if ( stream && stream->_ffmpeg && stream->_ffmpeg->codec )
{
AVRational frate;
#if LIBAVFORMAT_VERSION_INT < AV_VERSION_INT(55,12,100)
frate = stream->_ffmpeg->r_frame_rate;
#elif defined(av_stream_get_r_frame_rate)
frate = av_stream_get_r_frame_rate(stream->_ffmpeg);
#else
frate = stream->_ffmpeg->avg_frame_rate;
#endif
if ( nominator ) *nominator = frate.num;
if ( denominator ) *denominator = frate.den;
return ( float )frate.num / frate.den;
}
else
{
SDL_ffmpegSetError( "could not retreive frame rate from stream" );
if ( nominator ) *nominator = 0;
if ( denominator ) *denominator = 0;
}
return 0.0;
}
/** \brief This can be used to get a SDL_AudioSpec based on values found in file
This returns a SDL_AudioSpec, if you have selected a valid audio
stream. Otherwise, all values are set to NULL.
\param file SDL_ffmpegFile from which the information is required
\param samples Amount of samples required every time the callback is called.
Lower values mean less latency, but please note that SDL has a minimal value.
\param callback Pointer to callback function
\returns SDL_AudioSpec with values set according to the selected audio stream.
If no valid audio stream was available, all values of returned SDL_AudioSpec are set to 0
*/
SDL_AudioSpec SDL_ffmpegGetAudioSpec( SDL_ffmpegFile *file, uint16_t samples, SDL_ffmpegCallback callback )
{
/* create audio spec */
SDL_AudioSpec spec;
memset( &spec, 0, sizeof( SDL_AudioSpec ) );
if ( !file ) return spec;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
/* if we have a valid audiofile, we can use its data to create a
more appropriate audio spec */
if ( file->audioStream )
{
spec.format = AUDIO_S16SYS;
spec.samples = samples;
spec.userdata = file;
spec.callback = callback;
spec.freq = file->audioStream->_ffmpeg->codec->sample_rate;
spec.channels = ( uint8_t )file->audioStream->_ffmpeg->codec->channels;
}
else
{
SDL_ffmpegSetError( "no valid audio stream selected" );
}
SDL_UnlockMutex( file->streamMutex );
return spec;
}
/** \brief Returns the Duration of the file in milliseconds.
Please note that this value is guestimated by FFmpeg, it may differ from
actual playing time.
\param file SDL_ffmpegFile from which the information is required
\returns -1 on error, otherwise the length of the file in milliseconds
*/
uint64_t SDL_ffmpegDuration( SDL_ffmpegFile *file )
{
if ( !file ) return 0;
if ( file->type == SDL_ffmpegInputStream )
{
/* returns the duration of the entire file, please note that ffmpeg doesn't
always get this value right! so don't bet your life on it... */
return file->_ffmpeg->duration / ( AV_TIME_BASE / 1000 );
}
if ( file->type == SDL_ffmpegOutputStream )
{
uint64_t v = SDL_ffmpegVideoDuration( file );
uint64_t a = SDL_ffmpegAudioDuration( file );
if ( v > a ) return v;
return a;
}
return 0;
}
/** \brief Returns the duration of the audio stream in milliseconds.
This value can be used to sync two output streams.
\param file SDL_ffmpegFile from which the information is required
\returns -1 on error, otherwise the length of the file in milliseconds
*/
uint64_t SDL_ffmpegAudioDuration( SDL_ffmpegFile *file )
{
if ( !file ) return 0;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
uint64_t duration = 0;
if ( file->audioStream )
{
if ( file->type == SDL_ffmpegInputStream )
{
duration = av_rescale( 1000 * file->audioStream->_ffmpeg->duration, file->audioStream->_ffmpeg->time_base.num, file->audioStream->_ffmpeg->time_base.den );
}
else if ( file->type == SDL_ffmpegOutputStream )
{
duration = file->audioStream->frameCount * file->audioStream->encodeAudioInputSize / ( file->audioStream->_ffmpeg->codec->sample_rate / 1000 );
}
}
else
{
SDL_ffmpegSetError( "no valid audio stream selected" );
}
SDL_UnlockMutex( file->streamMutex );
return duration;
}
/** \brief Returns the duration of the video stream in milliseconds.
This value can be used to sync two output streams.
\param file SDL_ffmpegFile from which the information is required
\returns -1 on error, otherwise the length of the file in milliseconds
*/
uint64_t SDL_ffmpegVideoDuration( SDL_ffmpegFile *file )
{
if ( !file ) return 0;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
uint64_t duration = 0;
if ( file->videoStream )
{
if ( file->type == SDL_ffmpegInputStream )
{
duration = av_rescale( 1000 * file->videoStream->_ffmpeg->duration, file->videoStream->_ffmpeg->time_base.num, file->videoStream->_ffmpeg->time_base.den );
}
else if ( file->type == SDL_ffmpegOutputStream )
{
duration = av_rescale( 1000 * file->videoStream->frameCount, file->videoStream->_ffmpeg->codec->time_base.num, file->videoStream->_ffmpeg->codec->time_base.den );
}
}
else
{
SDL_ffmpegSetError( "no valid video stream selected" );
}
SDL_UnlockMutex( file->streamMutex );
return duration;
}
/** \brief retreive the width/height of a frame beloning to file
With this function you can get the width and height of a frame, belonging to
your file. If there is no (valid) videostream selected w and h default
to 0. Please not that you will have to make sure the pointers are
allocated.
\param file SDL_ffmpegFile from which the information is required
\param w width
\param h height
\returns -1 on error, otherwise 0
*/
int SDL_ffmpegGetVideoSize( SDL_ffmpegFile *file, int *w, int *h )
{
if ( !file || !w || !h ) return -1;
/* when accesing audio/video stream, streamMutex should be locked */
SDL_LockMutex( file->streamMutex );
/* if we have a valid video file selected, we use it
if not, we send default values and return.
by checking the return value you can check if you got a valid size */
if ( file->videoStream )
{
*w = file->videoStream->_ffmpeg->codec->width;
*h = file->videoStream->_ffmpeg->codec->height;
SDL_UnlockMutex( file->streamMutex );
return 0;
}
else
{
SDL_ffmpegSetError( "no valid video stream selected" );
}
*w = 0;
*h = 0;
SDL_UnlockMutex( file->streamMutex );
return -1;
}
/** \brief This is used to check if a valid audio stream is selected.
\param file SDL_ffmpegFile from which the information is required
\returns non-zero if a valid video stream is selected, otherwise 0
*/
int SDL_ffmpegValidAudio( SDL_ffmpegFile* file )
{
/* this function is used to check if we selected a valid audio stream */
return ( file && file->audioStream );
}
/** \brief This is used to check if a valid video stream is selected.
\param file SDL_ffmpegFile from which the information is required
\returns non-zero if a valid video stream is selected, otherwise 0
*/
int SDL_ffmpegValidVideo( SDL_ffmpegFile* file )
{
/* this function is used to check if we selected a valid video stream */
return ( file && file->videoStream );
}
#ifdef SDL_FF_WRITE
/** \brief This is used to add a video stream to file
\param file SDL_ffmpegFile to which the stream will be added
\param codec SDL_ffmpegCodec describing the type encoding to be used by this
stream.
\returns The stream which was added, or NULL if no stream could be added.
*/
SDL_ffmpegStream* SDL_ffmpegAddVideoStream( SDL_ffmpegFile *file, SDL_ffmpegCodec codec )
{
/* add a video stream */
AVStream *stream = av_new_stream( file->_ffmpeg, 0 );
if ( !stream )
{
SDL_ffmpegSetError( "could not allocate video stream" );
return 0;
}
stream->codec = avcodec_alloc_context();
avcodec_get_context_defaults2( stream->codec, AVMEDIA_TYPE_VIDEO );
if ( codec.videoCodecID < 0 )
{
stream->codec->codec_id = file->_ffmpeg->oformat->video_codec;
}
else
{
stream->codec->codec_id = ( enum CodecID ) codec.videoCodecID;
}
stream->codec->codec_type = AVMEDIA_TYPE_VIDEO;
stream->codec->bit_rate = codec.videoBitrate;
/* resolution must be a multiple of two */
stream->codec->width = codec.width;
stream->codec->height = codec.height;
/* set time_base */
stream->codec->time_base.num = codec.framerateNum;
stream->codec->time_base.den = codec.framerateDen;
/* emit one intra frame every twelve frames at most */
stream->codec->gop_size = 12;
/* set pixel format */
stream->codec->pix_fmt = AV_PIX_FMT_YUV420P;
/* set mpeg2 codec parameters */
if ( stream->codec->codec_id == AV_CODEC_ID_MPEG2VIDEO )
{
stream->codec->max_b_frames = 2;
}
/* set mpeg1 codec parameters */
if ( stream->codec->codec_id == AV_CODEC_ID_MPEG1VIDEO )
{
/* needed to avoid using macroblocks in which some coeffs overflow
this doesnt happen with normal video, it just happens here as the
motion of the chroma plane doesnt match the luma plane */
stream->codec->mb_decision = 2;
}
/* some formats want stream headers to be separate */
if ( file->_ffmpeg->oformat->flags & AVFMT_GLOBALHEADER )
{
stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
/* find the video encoder */
AVCodec *videoCodec = avcodec_find_encoder( stream->codec->codec_id );
if ( !videoCodec )
{
SDL_ffmpegSetError( "video codec not found" );
return 0;
}
/* open the codec */
if ( avcodec_open( stream->codec, videoCodec ) < 0 )
{
SDL_ffmpegSetError( "could not open video codec" );
return 0;
}
/* create a new stream */
SDL_ffmpegStream *str = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );
if ( str )
{
/* we set our stream to zero */
memset( str, 0, sizeof( SDL_ffmpegStream ) );
str->id = file->audioStreams + file->videoStreams;
/* _ffmpeg holds data about streamcodec */
str->_ffmpeg = stream;
str->mutex = SDL_CreateMutex();
str->encodeFrame = avcodec_alloc_frame();
uint8_t *picture_buf;
int size = avpicture_get_size( stream->codec->pix_fmt, stream->codec->width, stream->codec->height );
picture_buf = ( uint8_t* )av_malloc( size + FF_INPUT_BUFFER_PADDING_SIZE );
avpicture_fill(( AVPicture* )str->encodeFrame, picture_buf, stream->codec->pix_fmt, stream->codec->width, stream->codec->height );
str->encodeFrameBufferSize = stream->codec->width * stream->codec->height * 4 + FF_INPUT_BUFFER_PADDING_SIZE;
str->encodeFrameBuffer = ( uint8_t* )av_malloc( str->encodeFrameBufferSize );
file->videoStreams++;
/* find correct place to save the stream */
SDL_ffmpegStream **s = &file->vs;
while ( *s )
{
*s = ( *s )->next;
}
*s = str;
if ( av_set_parameters( file->_ffmpeg, 0 ) < 0 )
{
SDL_ffmpegSetError( "could not set encoding parameters" );
}
/* try to write a header */
av_write_header( file->_ffmpeg );
}
return str;
}
#endif
#ifdef SDL_FF_WRITE
/** \brief This is used to add a video stream to file
\param file SDL_ffmpegFile to which the stream will be added
\param codec SDL_ffmpegCodec describing the type encoding to be used by this
stream.
\returns The stream which was added, or NULL if no stream could be added.
*/
SDL_ffmpegStream* SDL_ffmpegAddAudioStream( SDL_ffmpegFile *file, SDL_ffmpegCodec codec )
{
// add an audio stream
AVStream *stream = av_new_stream( file->_ffmpeg, 1 );
if ( !stream )
{
SDL_ffmpegSetError( "could not allocate audio stream" );
return 0;
}
if ( codec.audioCodecID < 0 )
{
stream->codec->codec_id = file->_ffmpeg->oformat->audio_codec;
}
else
{
stream->codec->codec_id = ( enum CodecID ) codec.audioCodecID;
}
stream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
stream->codec->bit_rate = codec.audioBitrate;
stream->codec->sample_rate = codec.sampleRate;
stream->codec->sample_fmt = AV_SAMPLE_FMT_S16;
stream->codec->channels = codec.channels;
// find the audio encoder
AVCodec *audioCodec = avcodec_find_encoder( stream->codec->codec_id );
if ( !audioCodec )
{
SDL_ffmpegSetError( "audio codec not found" );
return 0;
}
// open the codec
if ( avcodec_open( stream->codec, audioCodec ) < 0 )
{
SDL_ffmpegSetError( "could not open audio codec" );
return 0;
}
/* some formats want stream headers to be separate */
if ( file->_ffmpeg->oformat->flags & AVFMT_GLOBALHEADER )
{
stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
/* create a new stream */
SDL_ffmpegStream *str = ( SDL_ffmpegStream* )malloc( sizeof( SDL_ffmpegStream ) );
if ( str )
{
str->_ffmpeg = stream;
/* we set our stream to zero */
memset( str, 0, sizeof( SDL_ffmpegStream ) );
str->id = file->audioStreams + file->videoStreams;
/* _ffmpeg holds data about streamcodec */
str->_ffmpeg = stream;
str->mutex = SDL_CreateMutex();
str->sampleBufferSize = 10000;
str->sampleBuffer = ( int8_t* )av_malloc( str->sampleBufferSize );
/* ugly hack for PCM codecs (will be removed ASAP with new PCM
support to compute the input frame size in samples */
if ( stream->codec->frame_size <= 1 )
{
str->encodeAudioInputSize = str->sampleBufferSize / stream->codec->channels;
switch ( stream->codec->codec_id )
{
case AV_CODEC_ID_PCM_S16LE:
case AV_CODEC_ID_PCM_S16BE:
case AV_CODEC_ID_PCM_U16LE:
case AV_CODEC_ID_PCM_U16BE:
str->encodeAudioInputSize >>= 1;
break;
default:
break;
}
}
else
{
str->encodeAudioInputSize = stream->codec->frame_size;
}
file->audioStreams++;
/* find correct place to save the stream */
SDL_ffmpegStream **s = &file->as;
while ( *s )
{
*s = ( *s )->next;
}
*s = str;
if ( av_set_parameters( file->_ffmpeg, 0 ) < 0 )
{
SDL_ffmpegSetError( "could not set encoding parameters" );
return 0;
}
/* try to write a header */
av_write_header( file->_ffmpeg );
}
return str;
}
#endif
/** \brief Use this function to query if an error occured
\returns non-zero when an error occured
*/
int SDL_ffmpegError()
{
return SDL_ffmpegErrorMessage[ 0 ];
}
/** \brief Use this function to get the last error string
\returns When no error was found, NULL is returned
*/
const char* SDL_ffmpegGetError()
{
return SDL_ffmpegErrorMessage;
}
/** \brief Use this function to clear all standing errors
*/
void SDL_ffmpegClearError()
{
SDL_ffmpegErrorMessage[ 0 ] = 0;
}
/**
\cond
*/
void SDL_ffmpegSetError( const char *error )
{
if ( !error ) return;
if ( snprintf( SDL_ffmpegErrorMessage, 512, "%s", error ) >= 511 )
{
SDL_ffmpegErrorMessage[ 511 ] = 0;
}
}
int SDL_ffmpegGetPacket( SDL_ffmpegFile *file )
{
/* entering this function, streamMutex should have been locked */
/* create a packet for our data */
AVPacket *pack = ( AVPacket* )av_malloc( sizeof( AVPacket ) );
/* initialize packet */
av_init_packet( pack );
/* read a packet from the file */
int decode = av_read_frame( file->_ffmpeg, pack );
/* if we did not get a packet, we probably reached the end of the file */
if ( decode < 0 )
{
av_free( pack );
/* signal EOF */
return 1;
}
/* we got a packet, lets handle it */
/* try to allocate the packet */
if ( av_dup_packet( pack ) )
{
/* error allocating packet */
av_free_packet( pack );
}
else
{
/* If it's a packet from either of our streams, return it */
if ( file->audioStream && pack->stream_index == file->audioStream->id )
{
/* prepare packet */
SDL_ffmpegPacket *temp = ( SDL_ffmpegPacket* )malloc( sizeof( SDL_ffmpegPacket ) );
// TODO check and handle the case where temp failed to malloc
temp->data = pack;
temp->next = 0;
SDL_ffmpegPacket **p = &file->audioStream->buffer;
while ( *p )
{
p = &( *p )->next;
}
*p = temp;
}
else if ( file->videoStream && pack->stream_index == file->videoStream->id )
{
/* prepare packet */
SDL_ffmpegPacket *temp = ( SDL_ffmpegPacket* )malloc( sizeof( SDL_ffmpegPacket ) );
temp->data = pack;
temp->next = 0;
// SDL_LockMutex( file->videoStream->mutex );
SDL_ffmpegPacket **p = &file->videoStream->buffer;
while ( *p )
{
p = &( *p )->next;
}
*p = temp;
// SDL_UnlockMutex( file->videoStream->mutex );
}
else
{
av_free_packet( pack );
}
}
return 0;
}
SDL_ffmpegPacket* SDL_ffmpegGetAudioPacket( SDL_ffmpegFile *file )
{
if ( !file->audioStream ) return 0;
/* file->audioStream->mutex should be locked before entering this function */
SDL_ffmpegPacket *pack = 0;
/* check if there are still packets in buffer */
if ( file->audioStream->buffer )
{
pack = file->audioStream->buffer;
file->audioStream->buffer = pack->next;
}
/* if a packet was found, return it */
return pack;
}
SDL_ffmpegPacket* SDL_ffmpegGetVideoPacket( SDL_ffmpegFile *file )
{
if ( !file->videoStream ) return 0;
/* file->videoStream->mutex should be locked before entering this function */
SDL_ffmpegPacket *pack = 0;
/* check if there are still packets in buffer */
if ( file->videoStream->buffer )
{
pack = file->videoStream->buffer;
file->videoStream->buffer = pack->next;
}
/* if a packet was found, return it */
return pack;
}
int SDL_ffmpegDecodeAudioFrame( SDL_ffmpegFile *file, AVPacket *pack, SDL_ffmpegAudioFrame *frame )
{
uint8_t *data = pack->data;
int size = pack->size;
int audioSize = AVCODEC_MAX_AUDIO_FRAME_SIZE * sizeof( int16_t );
int channels = file->audioStream->_ffmpeg->codec->channels;
enum AVSampleFormat in_fmt = file->audioStream->_ffmpeg->codec->sample_fmt;
int in_bps = av_get_bytes_per_sample(in_fmt);
enum AVSampleFormat out_fmt = AV_SAMPLE_FMT_S16;
int out_bps = av_get_bytes_per_sample(out_fmt);
/* check if there is still data in the buffer */
if ( file->audioStream->sampleBufferSize )
{
/* set new pts */
if ( !frame->size ) frame->pts = file->audioStream->sampleBufferTime;
/* calculate free space in frame */
int fs = frame->capacity - frame->size;
/* check the amount of data which needs to be copied */
int in_samples = file->audioStream->sampleBufferSize / (channels * in_bps);
int out_samples = fs / (channels * out_bps);
if (out_samples < in_samples)
{
/* copy data from sampleBuffer into frame buffer until frame buffer is full */
int written = convert_audio(out_samples, channels, file->audioStream->sampleBufferStride,
in_fmt, (uint8_t *)(file->audioStream->sampleBuffer + file->audioStream->sampleBufferOffset),
out_samples, channels, -1,
out_fmt, frame->buffer + frame->size);
/* mark the amount of bytes still in the buffer */
file->audioStream->sampleBufferSize -= out_samples * channels * in_bps;
/* move offset accordingly */
if (av_sample_fmt_is_planar(in_fmt))
file->audioStream->sampleBufferOffset += out_samples * in_bps;
else
file->audioStream->sampleBufferOffset += out_samples * in_bps * channels;
/* update framesize */
frame->size += written;
}
else
{
/* copy data from sampleBuffer into frame buffer until sampleBuffer is empty */
int written = convert_audio(in_samples, channels, file->audioStream->sampleBufferStride,
in_fmt, (uint8_t *)(file->audioStream->sampleBuffer + file->audioStream->sampleBufferOffset),
in_samples, channels, -1,
out_fmt, frame->buffer + frame->size);
/* update framesize */
frame->size += written;
/* at this point, samplebuffer should have been handled */
file->audioStream->sampleBufferSize = 0;
/* no more data in buffer, reset offset */
file->audioStream->sampleBufferOffset = 0;
}
/* return 0 to signal caller that 'pack' was not used */
if ( frame->size == frame->capacity ) return 0;
}
/* calculate pts to determine wheter or not this frame should be stored */
file->audioStream->sampleBufferTime = av_rescale(( pack->dts - file->audioStream->_ffmpeg->start_time ) * 1000, file->audioStream->_ffmpeg->time_base.num, file->audioStream->_ffmpeg->time_base.den );
while ( size > 0 )
{
/* Decode the packet */
AVCodecContext *avctx = file->audioStream->_ffmpeg->codec;
#if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1)
AVFrame *dframe = avcodec_alloc_frame();
avcodec_get_frame_defaults(dframe);
#else
AVFrame *dframe = av_frame_alloc();
#endif
int got_frame = 0;
int len = avcodec_decode_audio4( avctx, dframe, &got_frame, pack );
if (len < 0 || !got_frame)
{
SDL_ffmpegSetError( "error decoding audio frame" );
break;
}
int planar = av_sample_fmt_is_planar( avctx->sample_fmt );
int plane_size;
int data_size = av_samples_get_buffer_size( &plane_size, avctx->channels, dframe->nb_samples, avctx->sample_fmt, 1 );
if ( data_size > 10000 )
{
SDL_ffmpegSetError( "too much data in decoded audio frame" );
break;
}
memcpy( file->audioStream->sampleBuffer, dframe->extended_data[0], plane_size );
audioSize = plane_size;
if ( planar && avctx->channels > 1 )
{
int8_t *out = file->audioStream->sampleBuffer + plane_size;
int ch;
for ( ch = 1; ch < avctx->channels; ch++ )
{
memcpy( out, dframe->extended_data[ch], plane_size );
out += plane_size;
audioSize += plane_size;
}
}
/* change pointers */
data += len;
size -= len;
}
{
/* set new pts */
if ( !frame->size ) frame->pts = file->audioStream->sampleBufferTime;
/* save stride of data we just grabbed */
file->audioStream->sampleBufferStride = audioSize / channels;
/* room in frame */
int fs = frame->capacity - frame->size;
/* check if there is room at all */
if ( fs )
{
/* check the amount of data which needs to be copied */
int in_samples = audioSize / (channels * in_bps);
int out_samples = fs / (channels * out_bps);
if (out_samples < in_samples)
{
/* copy data from sampleBuffer into frame buffer until frame buffer is full */
int written = convert_audio(out_samples, channels, file->audioStream->sampleBufferStride,
in_fmt, (uint8_t *)(file->audioStream->sampleBuffer),
out_samples, channels, -1,
out_fmt, frame->buffer + frame->size);
/* mark the amount of bytes still in the buffer */
file->audioStream->sampleBufferSize = ((in_samples - out_samples) * channels * in_bps);
/* set the offset so the remaining data can be found */
if (av_sample_fmt_is_planar(in_fmt))
file->audioStream->sampleBufferOffset = out_samples * in_bps;
else
file->audioStream->sampleBufferOffset = out_samples * in_bps * channels;
/* update framesize */
frame->size += written;
}
else
{
/* copy data from sampleBuffer into frame buffer until sampleBuffer is empty */
int written = convert_audio(in_samples, channels, file->audioStream->sampleBufferStride,
in_fmt, (uint8_t *)(file->audioStream->sampleBuffer),
in_samples, channels, -1,
out_fmt, frame->buffer + frame->size);
/* mark the amount of bytes still in the buffer */
file->audioStream->sampleBufferSize = 0;
/* reset buffer offset */
file->audioStream->sampleBufferOffset = 0;
/* update framesize */
frame->size += written;
}
}
else
{
/* no room in frame, mark samplebuffer as full */
file->audioStream->sampleBufferSize = audioSize;
/* reset buffer offset */
file->audioStream->sampleBufferOffset = 0;
}
}
/* pack was used, return 1 */
return 1;
}
int SDL_ffmpegDecodeVideoFrame( SDL_ffmpegFile* file, AVPacket *pack, SDL_ffmpegVideoFrame *frame )
{
int got_frame = 0;
if ( pack )
{
/* usefull when dealing with B frames */
if ( pack->dts == AV_NOPTS_VALUE )
{
/* if we did not get a valid timestamp, we make one up based on the last
valid timestamp + the duration of a frame */
frame->pts = file->videoStream->lastTimeStamp + av_rescale( 1000 * pack->duration, file->videoStream->_ffmpeg->time_base.num, file->videoStream->_ffmpeg->time_base.den );
}
else
{
/* write timestamp into the buffer */
frame->pts = av_rescale(( pack->dts - file->videoStream->_ffmpeg->start_time ) * 1000, file->videoStream->_ffmpeg->time_base.num, file->videoStream->_ffmpeg->time_base.den );
}
/* Decode the packet */
#if ( ( LIBAVCODEC_VERSION_MAJOR <= 52 ) && ( LIBAVCODEC_VERSION_MINOR <= 20 ) )
avcodec_decode_video( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, pack->data, pack->size );
#else
avcodec_decode_video2( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, pack );
#endif
}
else
{
/* check if there is still a frame left in the buffer */
#if ( LIBAVCODEC_VERSION_MAJOR <= 52 && LIBAVCODEC_VERSION_MINOR <= 20 )
avcodec_decode_video( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, 0, 0 );
#else
AVPacket temp;
av_init_packet( &temp );
temp.data = 0;
temp.size = 0;
temp.stream_index = file->videoStream->_ffmpeg->index;
avcodec_decode_video2( file->videoStream->_ffmpeg->codec, file->videoStream->decodeFrame, &got_frame, &temp );
#endif
}
/* if we did not get a frame, we return */
if ( got_frame )
{
/* convert YUV 420 to YUYV 422 data */
// if ( frame->overlay && frame->overlay->format == SDL_YUY2_OVERLAY )
// {
// int pitch[] =
// {
// frame->overlay->pitches[ 0 ],
// frame->overlay->pitches[ 1 ],
// frame->overlay->pitches[ 2 ]
// };
//
// sws_scale( getContext( &file->videoStream->conversionContext,
// file->videoStream->_ffmpeg->codec->width,
// file->videoStream->_ffmpeg->codec->height,
// file->videoStream->_ffmpeg->codec->pix_fmt,
// frame->overlay->w, frame->overlay->h,
// AV_PIX_FMT_YUYV422 ),
// ( const uint8_t* const* )file->videoStream->decodeFrame->data,
// file->videoStream->decodeFrame->linesize,
// 0,
// file->videoStream->_ffmpeg->codec->height,
// ( uint8_t* const* )frame->overlay->pixels,
// pitch );
// }
/* convert YUV to RGB data */
if ( frame->surface && frame->surface->format )
{
int pitch = frame->surface->pitch;
switch ( frame->surface->format->BitsPerPixel )
{
case 32:
sws_scale( getContext( &file->videoStream->conversionContext,
file->videoStream->_ffmpeg->codec->width,
file->videoStream->_ffmpeg->codec->height,
file->videoStream->_ffmpeg->codec->pix_fmt,
frame->surface->w, frame->surface->h,
AV_PIX_FMT_RGB32 ),
( const uint8_t* const* )file->videoStream->decodeFrame->data,
file->videoStream->decodeFrame->linesize,
0,
file->videoStream->_ffmpeg->codec->height,
( uint8_t* const* )&frame->surface->pixels,
&pitch );
break;
case 24:
sws_scale( getContext( &file->videoStream->conversionContext,
file->videoStream->_ffmpeg->codec->width,
file->videoStream->_ffmpeg->codec->height,
file->videoStream->_ffmpeg->codec->pix_fmt,
frame->surface->w, frame->surface->h,
AV_PIX_FMT_RGB24 ),
( const uint8_t* const* )file->videoStream->decodeFrame->data,
file->videoStream->decodeFrame->linesize,
0,
file->videoStream->_ffmpeg->codec->height,
( uint8_t* const* )&frame->surface->pixels,
&pitch );
break;
default:
break;
}
}
/* we write the lastTimestamp we got */
file->videoStream->lastTimeStamp = frame->pts;
/* flag this frame as ready */
frame->ready = 1;
}
return frame->ready;
}
/**
\endcond
*/