mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-30 15:18:42 +02:00
integrate OD FFmpeg to work with multiple formats, including wav
also move streamContext to ffmpeg.h to allow for refactoring of import code
This commit is contained in:
parent
8cb2a38fbe
commit
816b58bc08
130
src/FFmpeg.cpp
130
src/FFmpeg.cpp
@ -420,6 +420,136 @@ fail:
|
|||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
streamContext *import_ffmpeg_read_next_frame(AVFormatContext* formatContext,
|
||||||
|
streamContext** streams,
|
||||||
|
unsigned int numStreams)
|
||||||
|
{
|
||||||
|
streamContext *sc = NULL;
|
||||||
|
AVPacket pkt;
|
||||||
|
|
||||||
|
if (av_read_frame(formatContext,&pkt) < 0)
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find a stream to which this frame belongs to
|
||||||
|
for (int i = 0; i < numStreams; i++)
|
||||||
|
{
|
||||||
|
if (streams[i]->m_stream->index == pkt.stream_index)
|
||||||
|
sc = streams[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Off-stream packet. Don't panic, just skip it.
|
||||||
|
// When not all streams are selected for import this will happen very often.
|
||||||
|
if (sc == NULL)
|
||||||
|
{
|
||||||
|
av_free_packet(&pkt);
|
||||||
|
return (streamContext*)1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy the frame to the stream context
|
||||||
|
memcpy(&sc->m_pkt, &pkt, sizeof(AVPacket));
|
||||||
|
|
||||||
|
sc->m_pktValid = 1;
|
||||||
|
sc->m_pktDataPtr = pkt.data;
|
||||||
|
sc->m_pktRemainingSiz = pkt.size;
|
||||||
|
|
||||||
|
return sc;
|
||||||
|
}
|
||||||
|
|
||||||
|
int import_ffmpeg_decode_frame(streamContext *sc, bool flushing)
|
||||||
|
{
|
||||||
|
int nBytesDecoded;
|
||||||
|
wxUint8 *pDecode = sc->m_pktDataPtr;
|
||||||
|
int nDecodeSiz = sc->m_pktRemainingSiz;
|
||||||
|
|
||||||
|
sc->m_frameValid = 0;
|
||||||
|
|
||||||
|
if (flushing)
|
||||||
|
{
|
||||||
|
// If we're flushing the decoders we don't actually have any new data to decode.
|
||||||
|
pDecode = NULL;
|
||||||
|
nDecodeSiz = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (!sc->m_pktValid || (sc->m_pktRemainingSiz <= 0))
|
||||||
|
{
|
||||||
|
//No more data
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sc->m_samplefmt = sc->m_codecCtx->sample_fmt;
|
||||||
|
sc->m_samplesize = av_get_bits_per_sample_fmt(sc->m_samplefmt) / 8;
|
||||||
|
|
||||||
|
unsigned int newsize = FFMAX(sc->m_pkt.size * sc->m_samplesize, AVCODEC_MAX_AUDIO_FRAME_SIZE);
|
||||||
|
// Reallocate the audio sample buffer if it's smaller than the frame size.
|
||||||
|
if (newsize > sc->m_decodedAudioSamplesSiz )
|
||||||
|
{
|
||||||
|
if (sc->m_decodedAudioSamples)
|
||||||
|
{
|
||||||
|
av_free(sc->m_decodedAudioSamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
sc->m_decodedAudioSamples = (uint8_t *) av_malloc(newsize);
|
||||||
|
sc->m_decodedAudioSamplesSiz = newsize;
|
||||||
|
|
||||||
|
if (sc->m_decodedAudioSamples == NULL)
|
||||||
|
{
|
||||||
|
//Can't allocate bytes
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sc->m_decodedAudioSamplesValidSiz = sc->m_decodedAudioSamplesSiz;
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(52, 25, 0)
|
||||||
|
// avcodec_decode_audio3() expects the size of the output buffer as the 3rd parameter but
|
||||||
|
// also returns the number of bytes it decoded in the same parameter.
|
||||||
|
AVPacket avpkt;
|
||||||
|
av_init_packet(&avpkt);
|
||||||
|
avpkt.data = pDecode;
|
||||||
|
avpkt.size = nDecodeSiz;
|
||||||
|
nBytesDecoded =
|
||||||
|
avcodec_decode_audio3(sc->m_codecCtx,
|
||||||
|
(int16_t *)sc->m_decodedAudioSamples, // out
|
||||||
|
&sc->m_decodedAudioSamplesValidSiz, // in/out
|
||||||
|
&avpkt); // in
|
||||||
|
#else
|
||||||
|
// avcodec_decode_audio2() expects the size of the output buffer as the 3rd parameter but
|
||||||
|
// also returns the number of bytes it decoded in the same parameter.
|
||||||
|
nBytesDecoded =
|
||||||
|
avcodec_decode_audio2(sc->m_codecCtx,
|
||||||
|
(int16_t *) sc->m_decodedAudioSamples, // out
|
||||||
|
&sc->m_decodedAudioSamplesValidSiz, // in/out
|
||||||
|
pDecode, // in
|
||||||
|
nDecodeSiz); // in
|
||||||
|
#endif
|
||||||
|
if (nBytesDecoded < 0)
|
||||||
|
{
|
||||||
|
// Decoding failed. Don't stop.
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We may not have read all of the data from this packet. If so, the user can call again.
|
||||||
|
// Whether or not they do depends on if m_pktRemainingSiz == 0 (they can check).
|
||||||
|
sc->m_pktDataPtr += nBytesDecoded;
|
||||||
|
sc->m_pktRemainingSiz -= nBytesDecoded;
|
||||||
|
|
||||||
|
// At this point it's normally safe to assume that we've read some samples. However, the MPEG
|
||||||
|
// audio decoder is broken. If this is the case then we just return with m_frameValid == 0
|
||||||
|
// but m_pktRemainingSiz perhaps != 0, so the user can call again.
|
||||||
|
if (sc->m_decodedAudioSamplesValidSiz > 0)
|
||||||
|
{
|
||||||
|
sc->m_frameValid = 1;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/*******************************************************/
|
/*******************************************************/
|
||||||
|
|
||||||
class FFmpegNotFoundDialog;
|
class FFmpegNotFoundDialog;
|
||||||
|
12
src/FFmpeg.h
12
src/FFmpeg.h
@ -105,6 +105,7 @@ void FFmpegStartup();
|
|||||||
|
|
||||||
bool LoadFFmpeg(bool showerror);
|
bool LoadFFmpeg(bool showerror);
|
||||||
|
|
||||||
|
|
||||||
/// If Audacity failed to load libav*, this dialog
|
/// If Audacity failed to load libav*, this dialog
|
||||||
/// shows up and tells user about that. It will pop-up
|
/// shows up and tells user about that. It will pop-up
|
||||||
/// again and again until it is disabled.
|
/// again and again until it is disabled.
|
||||||
@ -330,8 +331,6 @@ void DropFFmpegLibs();
|
|||||||
int ufile_fopen(AVIOContext **s, const wxString & name, int flags);
|
int ufile_fopen(AVIOContext **s, const wxString & name, int flags);
|
||||||
int ufile_fopen_input(AVFormatContext **ic_ptr, wxString & name);
|
int ufile_fopen_input(AVFormatContext **ic_ptr, wxString & name);
|
||||||
|
|
||||||
//moving from ImportFFmpeg.cpp to FFMpeg.h so other cpp files can use this struct.
|
|
||||||
#ifdef EXPERIMENTAL_OD_FFMPEG
|
|
||||||
typedef struct _streamContext
|
typedef struct _streamContext
|
||||||
{
|
{
|
||||||
bool m_use; // TRUE = this stream will be loaded into Audacity
|
bool m_use; // TRUE = this stream will be loaded into Audacity
|
||||||
@ -359,7 +358,14 @@ typedef struct _streamContext
|
|||||||
sampleFormat m_osamplefmt; // output sample format
|
sampleFormat m_osamplefmt; // output sample format
|
||||||
|
|
||||||
} streamContext;
|
} streamContext;
|
||||||
#endif
|
|
||||||
|
// common utility functions
|
||||||
|
// utility calls that are shared with ImportFFmpeg and ODDecodeFFmpegTask
|
||||||
|
streamContext *import_ffmpeg_read_next_frame(AVFormatContext* formatContext,
|
||||||
|
streamContext** streams,
|
||||||
|
unsigned int numStreams);
|
||||||
|
|
||||||
|
int import_ffmpeg_decode_frame(streamContext *sc, bool flushing);
|
||||||
|
|
||||||
extern "C" {
|
extern "C" {
|
||||||
// A little explanation of what's going on here.
|
// A little explanation of what's going on here.
|
||||||
|
@ -46,8 +46,10 @@ ODDecodeBlockFile::ODDecodeBlockFile(wxFileName baseFileName,wxFileName audioFil
|
|||||||
mAliasStart(aliasStart),
|
mAliasStart(aliasStart),
|
||||||
mAliasChannel(aliasChannel)
|
mAliasChannel(aliasChannel)
|
||||||
{
|
{
|
||||||
|
mDecoder = NULL;
|
||||||
mDataAvailable=false;
|
mDataAvailable=false;
|
||||||
mAudioFileName = audioFileName;
|
mAudioFileName = audioFileName;
|
||||||
|
mFormat = int16Sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create the memory structure to refer to the given block file
|
/// Create the memory structure to refer to the given block file
|
||||||
@ -60,8 +62,10 @@ ODDecodeBlockFile::ODDecodeBlockFile(wxFileName existingFile, wxFileName audioFi
|
|||||||
mAliasStart(aliasStart),
|
mAliasStart(aliasStart),
|
||||||
mAliasChannel(aliasChannel)
|
mAliasChannel(aliasChannel)
|
||||||
{
|
{
|
||||||
|
mDecoder = NULL;
|
||||||
mDataAvailable=dataAvailable;
|
mDataAvailable=dataAvailable;
|
||||||
mAudioFileName = audioFileName;
|
mAudioFileName = audioFileName;
|
||||||
|
mFormat = int16Sample;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -324,8 +328,10 @@ int ODDecodeBlockFile::WriteODDecodeBlockFile()
|
|||||||
ret = mDecoder->Decode(sampleData, mFormat, mAliasStart, mLen, mAliasChannel);
|
ret = mDecoder->Decode(sampleData, mFormat, mAliasStart, mLen, mAliasChannel);
|
||||||
|
|
||||||
mDecoderMutex.Unlock();
|
mDecoderMutex.Unlock();
|
||||||
if(ret < 0)
|
if(ret < 0) {
|
||||||
|
printf("ODDecodeBlockFile Decode failure\n");
|
||||||
return ret; //failure
|
return ret; //failure
|
||||||
|
}
|
||||||
|
|
||||||
//the summary is also calculated here.
|
//the summary is also calculated here.
|
||||||
mFileNameMutex.Lock();
|
mFileNameMutex.Lock();
|
||||||
@ -559,7 +565,7 @@ bool ODDecodeBlockFile::ReadSummary(void *data)
|
|||||||
void ODDecodeBlockFile::SetODFileDecoder(ODFileDecoder* decoder)
|
void ODDecodeBlockFile::SetODFileDecoder(ODFileDecoder* decoder)
|
||||||
{
|
{
|
||||||
//since this is the only place that writes to mdecoder, it is totally thread-safe to read check without the mutex
|
//since this is the only place that writes to mdecoder, it is totally thread-safe to read check without the mutex
|
||||||
if(decoder==mDecoder)
|
if(decoder == mDecoder)
|
||||||
return;
|
return;
|
||||||
mDecoderMutex.Lock();
|
mDecoderMutex.Lock();
|
||||||
mDecoder = decoder;
|
mDecoder = decoder;
|
||||||
|
@ -162,36 +162,7 @@ static const wxChar *exts[] =
|
|||||||
extern FFmpegLibs *FFmpegLibsInst;
|
extern FFmpegLibs *FFmpegLibsInst;
|
||||||
|
|
||||||
class FFmpegImportFileHandle;
|
class FFmpegImportFileHandle;
|
||||||
//moving from ImportFFmpeg.cpp to FFMpeg.h so other cpp files can use this struct.
|
|
||||||
#ifndef EXPERIMENTAL_OD_FFMPEG
|
|
||||||
typedef struct _streamContext
|
|
||||||
{
|
|
||||||
bool m_use; // TRUE = this stream will be loaded into Audacity
|
|
||||||
AVStream *m_stream; // an AVStream *
|
|
||||||
AVCodecContext *m_codecCtx; // pointer to m_stream->codec
|
|
||||||
|
|
||||||
AVPacket m_pkt; // the last AVPacket we read for this stream
|
|
||||||
int m_pktValid; // is m_pkt valid?
|
|
||||||
uint8_t *m_pktDataPtr; // pointer into m_pkt.data
|
|
||||||
int m_pktRemainingSiz;
|
|
||||||
|
|
||||||
int64_t m_pts; // the current presentation time of the input stream
|
|
||||||
int64_t m_ptsOffset; // packets associated with stream are relative to this
|
|
||||||
|
|
||||||
int m_frameValid; // is m_decodedVideoFrame/m_decodedAudioSamples valid?
|
|
||||||
uint8_t *m_decodedAudioSamples; // decoded audio samples stored here
|
|
||||||
unsigned int m_decodedAudioSamplesSiz; // current size of m_decodedAudioSamples
|
|
||||||
int m_decodedAudioSamplesValidSiz; // # valid bytes in m_decodedAudioSamples
|
|
||||||
int m_initialchannels; // number of channels allocated when we begin the importing. Assumes that number of channels doesn't change on the fly.
|
|
||||||
|
|
||||||
int m_samplesize; // input sample size in bytes
|
|
||||||
SampleFormat m_samplefmt; // input sample format
|
|
||||||
|
|
||||||
int m_osamplesize; // output sample size in bytes
|
|
||||||
sampleFormat m_osamplefmt; // output sample format
|
|
||||||
|
|
||||||
} streamContext;
|
|
||||||
#endif
|
|
||||||
/// A representative of FFmpeg loader in
|
/// A representative of FFmpeg loader in
|
||||||
/// the Audacity import plugin list
|
/// the Audacity import plugin list
|
||||||
class FFmpegImportPlugin : public ImportPlugin
|
class FFmpegImportPlugin : public ImportPlugin
|
||||||
@ -730,128 +701,12 @@ int FFmpegImportFileHandle::Import(TrackFactory *trackFactory,
|
|||||||
|
|
||||||
streamContext *FFmpegImportFileHandle::ReadNextFrame()
|
streamContext *FFmpegImportFileHandle::ReadNextFrame()
|
||||||
{
|
{
|
||||||
streamContext *sc = NULL;
|
return import_ffmpeg_read_next_frame(mFormatContext, mScs, mNumStreams);
|
||||||
AVPacket pkt;
|
|
||||||
|
|
||||||
if (av_read_frame(mFormatContext,&pkt) < 0)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find a stream to which this frame belongs to
|
|
||||||
for (int i = 0; i < mNumStreams; i++)
|
|
||||||
{
|
|
||||||
if (mScs[i]->m_stream->index == pkt.stream_index)
|
|
||||||
sc = mScs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Off-stream packet. Don't panic, just skip it.
|
|
||||||
// When not all streams are selected for import this will happen very often.
|
|
||||||
if (sc == NULL)
|
|
||||||
{
|
|
||||||
av_free_packet(&pkt);
|
|
||||||
return (streamContext*)1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the frame to the stream context
|
|
||||||
memcpy(&sc->m_pkt, &pkt, sizeof(AVPacket));
|
|
||||||
|
|
||||||
sc->m_pktValid = 1;
|
|
||||||
sc->m_pktDataPtr = pkt.data;
|
|
||||||
sc->m_pktRemainingSiz = pkt.size;
|
|
||||||
|
|
||||||
return sc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int FFmpegImportFileHandle::DecodeFrame(streamContext *sc, bool flushing)
|
int FFmpegImportFileHandle::DecodeFrame(streamContext *sc, bool flushing)
|
||||||
{
|
{
|
||||||
int nBytesDecoded;
|
return import_ffmpeg_decode_frame(sc, flushing);
|
||||||
wxUint8 *pDecode = sc->m_pktDataPtr;
|
|
||||||
int nDecodeSiz = sc->m_pktRemainingSiz;
|
|
||||||
|
|
||||||
sc->m_frameValid = 0;
|
|
||||||
|
|
||||||
if (flushing)
|
|
||||||
{
|
|
||||||
// If we're flushing the decoders we don't actually have any new data to decode.
|
|
||||||
pDecode = NULL;
|
|
||||||
nDecodeSiz = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!sc->m_pktValid || (sc->m_pktRemainingSiz <= 0))
|
|
||||||
{
|
|
||||||
//No more data
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sc->m_samplefmt = sc->m_codecCtx->sample_fmt;
|
|
||||||
sc->m_samplesize = av_get_bits_per_sample_fmt(sc->m_samplefmt) / 8;
|
|
||||||
|
|
||||||
unsigned int newsize = FFMAX(sc->m_pkt.size * sc->m_samplesize, AVCODEC_MAX_AUDIO_FRAME_SIZE);
|
|
||||||
// Reallocate the audio sample buffer if it's smaller than the frame size.
|
|
||||||
if (newsize > sc->m_decodedAudioSamplesSiz )
|
|
||||||
{
|
|
||||||
if (sc->m_decodedAudioSamples)
|
|
||||||
{
|
|
||||||
av_free(sc->m_decodedAudioSamples);
|
|
||||||
}
|
|
||||||
|
|
||||||
sc->m_decodedAudioSamples = (uint8_t *) av_malloc(newsize);
|
|
||||||
sc->m_decodedAudioSamplesSiz = newsize;
|
|
||||||
|
|
||||||
if (sc->m_decodedAudioSamples == NULL)
|
|
||||||
{
|
|
||||||
//Can't allocate bytes
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
sc->m_decodedAudioSamplesValidSiz = sc->m_decodedAudioSamplesSiz;
|
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(52, 25, 0)
|
|
||||||
// avcodec_decode_audio3() expects the size of the output buffer as the 3rd parameter but
|
|
||||||
// also returns the number of bytes it decoded in the same parameter.
|
|
||||||
AVPacket avpkt;
|
|
||||||
av_init_packet(&avpkt);
|
|
||||||
avpkt.data = pDecode;
|
|
||||||
avpkt.size = nDecodeSiz;
|
|
||||||
nBytesDecoded =
|
|
||||||
avcodec_decode_audio3(sc->m_codecCtx,
|
|
||||||
(int16_t *)sc->m_decodedAudioSamples, // out
|
|
||||||
&sc->m_decodedAudioSamplesValidSiz, // in/out
|
|
||||||
&avpkt); // in
|
|
||||||
#else
|
|
||||||
// avcodec_decode_audio2() expects the size of the output buffer as the 3rd parameter but
|
|
||||||
// also returns the number of bytes it decoded in the same parameter.
|
|
||||||
nBytesDecoded =
|
|
||||||
avcodec_decode_audio2(sc->m_codecCtx,
|
|
||||||
(int16_t *) sc->m_decodedAudioSamples, // out
|
|
||||||
&sc->m_decodedAudioSamplesValidSiz, // in/out
|
|
||||||
pDecode, // in
|
|
||||||
nDecodeSiz); // in
|
|
||||||
#endif
|
|
||||||
if (nBytesDecoded < 0)
|
|
||||||
{
|
|
||||||
// Decoding failed. Don't stop.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We may not have read all of the data from this packet. If so, the user can call again.
|
|
||||||
// Whether or not they do depends on if m_pktRemainingSiz == 0 (they can check).
|
|
||||||
sc->m_pktDataPtr += nBytesDecoded;
|
|
||||||
sc->m_pktRemainingSiz -= nBytesDecoded;
|
|
||||||
|
|
||||||
// At this point it's normally safe to assume that we've read some samples. However, the MPEG
|
|
||||||
// audio decoder is broken. If this is the case then we just return with m_frameValid == 0
|
|
||||||
// but m_pktRemainingSiz perhaps != 0, so the user can call again.
|
|
||||||
if (sc->m_decodedAudioSamplesValidSiz > 0)
|
|
||||||
{
|
|
||||||
sc->m_frameValid = 1;
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int FFmpegImportFileHandle::WriteData(streamContext *sc)
|
int FFmpegImportFileHandle::WriteData(streamContext *sc)
|
||||||
|
@ -13,6 +13,8 @@
|
|||||||
|
|
||||||
#include "../Audacity.h" // needed before FFmpeg.h
|
#include "../Audacity.h" // needed before FFmpeg.h
|
||||||
#include "../FFmpeg.h" // which brings in avcodec.h, avformat.h
|
#include "../FFmpeg.h" // which brings in avcodec.h, avformat.h
|
||||||
|
#include "../import/ImportFFmpeg.h"
|
||||||
|
|
||||||
#ifndef WX_PRECOMP
|
#ifndef WX_PRECOMP
|
||||||
// Include your minimal set of headers here, or wx.h
|
// Include your minimal set of headers here, or wx.h
|
||||||
#include <wx/window.h>
|
#include <wx/window.h>
|
||||||
@ -32,10 +34,12 @@ extern FFmpegLibs *FFmpegLibsInst;
|
|||||||
//struct for caching the decoded samples to be used over multiple blockfiles
|
//struct for caching the decoded samples to be used over multiple blockfiles
|
||||||
typedef struct _FFmpegDecodeCache
|
typedef struct _FFmpegDecodeCache
|
||||||
{
|
{
|
||||||
int16_t* samplePtr;//interleaved samples - currently ffmpeg only uses 16 bit int
|
uint8_t* samplePtr;//interleaved samples
|
||||||
sampleCount start;
|
sampleCount start;
|
||||||
sampleCount len;
|
sampleCount len;
|
||||||
int numChannels;
|
int numChannels;
|
||||||
|
SampleFormat samplefmt; // input (from libav) sample format
|
||||||
|
|
||||||
} FFMpegDecodeCache;
|
} FFMpegDecodeCache;
|
||||||
|
|
||||||
|
|
||||||
@ -70,7 +74,7 @@ private:
|
|||||||
void InsertCache(FFMpegDecodeCache* cache);
|
void InsertCache(FFMpegDecodeCache* cache);
|
||||||
|
|
||||||
//puts the actual audio samples into the blockfile's data array
|
//puts the actual audio samples into the blockfile's data array
|
||||||
int FillDataFromCache(samplePtr & data, sampleCount & start, sampleCount& len, unsigned int channel);
|
int FillDataFromCache(samplePtr & data, sampleFormat outFormat, sampleCount & start, sampleCount& len, unsigned int channel);
|
||||||
///REFACTORABLE CODE FROM IMPORT FFMPEG
|
///REFACTORABLE CODE FROM IMPORT FFMPEG
|
||||||
///! Reads next audio frame
|
///! Reads next audio frame
|
||||||
///\return pointer to the stream context structure to which the frame belongs to or NULL on error, or 1 if stream is not to be imported.
|
///\return pointer to the stream context structure to which the frame belongs to or NULL on error, or 1 if stream is not to be imported.
|
||||||
@ -262,8 +266,7 @@ ODFFmpegDecoder::~ODFFmpegDecoder()
|
|||||||
#define kMaxSeekRewindAttempts 8
|
#define kMaxSeekRewindAttempts 8
|
||||||
int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel)
|
int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount start, sampleCount len, unsigned int channel)
|
||||||
{
|
{
|
||||||
//it looks like the code in importFFmpeg.cpp only imports 16 bit int - need to see why.
|
format = mScs[mStreamIndex]->m_osamplefmt;
|
||||||
format = int16Sample;
|
|
||||||
|
|
||||||
data = NewSamples(len, format);
|
data = NewSamples(len, format);
|
||||||
samplePtr bufStart = data;
|
samplePtr bufStart = data;
|
||||||
@ -276,7 +279,7 @@ int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
|||||||
if(mCurrentPos > start && mCurrentPos <= start+len + kDecodeSampleAllowance)
|
if(mCurrentPos > start && mCurrentPos <= start+len + kDecodeSampleAllowance)
|
||||||
{
|
{
|
||||||
//this next call takes data, start and len as reference variables and updates them to reflect the new area that is needed.
|
//this next call takes data, start and len as reference variables and updates them to reflect the new area that is needed.
|
||||||
FillDataFromCache(bufStart,start,len,channel);
|
FillDataFromCache(bufStart, format, start,len,channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -287,7 +290,7 @@ int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
|||||||
int stindex = -1;
|
int stindex = -1;
|
||||||
uint64_t targetts;
|
uint64_t targetts;
|
||||||
|
|
||||||
printf("attempting seek to %llu\n", start);
|
//printf("attempting seek to %llu\n", start);
|
||||||
//we have to find the index for this stream.
|
//we have to find the index for this stream.
|
||||||
for (unsigned int i = 0; i < mFormatContext->nb_streams; i++) {
|
for (unsigned int i = 0; i < mFormatContext->nb_streams; i++) {
|
||||||
if (mFormatContext->streams[i] == sc->m_stream )
|
if (mFormatContext->streams[i] == sc->m_stream )
|
||||||
@ -310,7 +313,7 @@ int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
|||||||
|
|
||||||
mCurrentPos = actualDecodeStart;
|
mCurrentPos = actualDecodeStart;
|
||||||
//if the seek was past our desired position, rewind a bit.
|
//if the seek was past our desired position, rewind a bit.
|
||||||
printf("seek ok to %llu samps, float: %f\n",actualDecodeStart,actualDecodeStartDouble);
|
//printf("seek ok to %llu samps, float: %f\n",actualDecodeStart,actualDecodeStartDouble);
|
||||||
} else
|
} else
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -336,21 +339,35 @@ int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
|||||||
double actualDecodeStartdouble = 0.52 + sc->m_stream->codec->sample_rate * sc->m_pkt.dts * ((double)sc->m_stream->time_base.num/sc->m_stream->time_base.den); //this is mostly safe because den is usually 1 or low number but check for high values.
|
double actualDecodeStartdouble = 0.52 + sc->m_stream->codec->sample_rate * sc->m_pkt.dts * ((double)sc->m_stream->time_base.num/sc->m_stream->time_base.den); //this is mostly safe because den is usually 1 or low number but check for high values.
|
||||||
|
|
||||||
//hack to get rounding to work to neareset frame size since dts isn't exact
|
//hack to get rounding to work to neareset frame size since dts isn't exact
|
||||||
actualDecodeStart = ((actualDecodeStart + sc->m_stream->codec->frame_size/2) / sc->m_stream->codec->frame_size) * sc->m_stream->codec->frame_size;
|
if (sc->m_stream->codec->frame_size) {
|
||||||
|
actualDecodeStart = ((actualDecodeStart + sc->m_stream->codec->frame_size/2) / sc->m_stream->codec->frame_size) * sc->m_stream->codec->frame_size;
|
||||||
|
}
|
||||||
|
|
||||||
if(actualDecodeStart != mCurrentPos)
|
if(actualDecodeStart != mCurrentPos)
|
||||||
printf("ts not matching - now:%llu (%f), last:%llu, lastlen:%llu, start %llu, len %llu\n",actualDecodeStart, actualDecodeStartdouble, mCurrentPos, mCurrentLen, start, len);
|
//printf("ts not matching - now:%llu (%f), last:%llu, lastlen:%llu, start %llu, len %llu\n",actualDecodeStart, actualDecodeStartdouble, mCurrentPos, mCurrentLen, start, len);
|
||||||
//if we've skipped over some samples, fill the gap with silence. This could happen often in the beginning of the file.
|
//if we've skipped over some samples, fill the gap with silence. This could happen often in the beginning of the file.
|
||||||
if(actualDecodeStart>start && firstpass) {
|
if(actualDecodeStart>start && firstpass) {
|
||||||
|
// find the number of samples for the leading silence
|
||||||
int amt = actualDecodeStart - start;
|
int amt = actualDecodeStart - start;
|
||||||
FFMpegDecodeCache* cache = new FFMpegDecodeCache;
|
FFMpegDecodeCache* cache = new FFMpegDecodeCache;
|
||||||
|
|
||||||
printf("skipping/zeroing %i samples. - now:%llu (%f), last:%llu, lastlen:%llu, start %llu, len %llu\n",amt,actualDecodeStart, actualDecodeStartdouble, mCurrentPos, mCurrentLen, start, len);
|
//printf("skipping/zeroing %i samples. - now:%llu (%f), last:%llu, lastlen:%llu, start %llu, len %llu\n",amt,actualDecodeStart, actualDecodeStartdouble, mCurrentPos, mCurrentLen, start, len);
|
||||||
|
|
||||||
//put it in the cache so the other channels can use it.
|
//put it in the cache so the other channels can use it.
|
||||||
cache->numChannels = sc->m_stream->codec->channels;
|
cache->numChannels = sc->m_stream->codec->channels;
|
||||||
cache->len = amt;
|
cache->len = amt;
|
||||||
cache->start=start;
|
cache->start=start;
|
||||||
cache->samplePtr = (int16_t*) malloc(amt * cache->numChannels * sizeof(int16_t));
|
// 8 bit and 16 bit audio output from ffmpeg means
|
||||||
memset(cache->samplePtr, 0, amt * cache->numChannels * sizeof(int16_t));
|
// 16 bit int out.
|
||||||
|
// 32 bit int, float, double mean float out.
|
||||||
|
if (format == int16Sample)
|
||||||
|
cache->samplefmt = SAMPLE_FMT_S16;
|
||||||
|
else
|
||||||
|
cache->samplefmt = SAMPLE_FMT_FLT;
|
||||||
|
|
||||||
|
cache->samplePtr = (uint8_t*) malloc(amt * cache->numChannels * SAMPLE_SIZE(format));
|
||||||
|
|
||||||
|
memset(cache->samplePtr, 0, amt * cache->numChannels * SAMPLE_SIZE(format));
|
||||||
|
|
||||||
InsertCache(cache);
|
InsertCache(cache);
|
||||||
}
|
}
|
||||||
@ -389,7 +406,7 @@ int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
|||||||
}
|
}
|
||||||
|
|
||||||
//this next call takes data, start and len as reference variables and updates them to reflect the new area that is needed.
|
//this next call takes data, start and len as reference variables and updates them to reflect the new area that is needed.
|
||||||
FillDataFromCache(bufStart,start,len,channel);
|
FillDataFromCache(bufStart, format, start, len, channel);
|
||||||
|
|
||||||
//if for some reason we couldn't get the samples, fill them with silence
|
//if for some reason we couldn't get the samples, fill them with silence
|
||||||
int16_t* outBuf = (int16_t*) bufStart;
|
int16_t* outBuf = (int16_t*) bufStart;
|
||||||
@ -404,11 +421,10 @@ int ODFFmpegDecoder::Decode(samplePtr & data, sampleFormat & format, sampleCount
|
|||||||
#define kODFFmpegSearchThreshold 10
|
#define kODFFmpegSearchThreshold 10
|
||||||
///returns the number of samples filled in from start.
|
///returns the number of samples filled in from start.
|
||||||
//also updates data and len to reflect new unfilled area - start is unmodified.
|
//also updates data and len to reflect new unfilled area - start is unmodified.
|
||||||
int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount &start, sampleCount& len, unsigned int channel)
|
int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleFormat outFormat, sampleCount &start, sampleCount& len, unsigned int channel)
|
||||||
{
|
{
|
||||||
if(mDecodeCache.size() <= 0)
|
if(mDecodeCache.size() <= 0)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
int samplesFilled=0;
|
int samplesFilled=0;
|
||||||
|
|
||||||
//do a search for the best position to start at.
|
//do a search for the best position to start at.
|
||||||
@ -442,11 +458,10 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount &start, sam
|
|||||||
//we only accept cache hits that touch either end - no piecing out of the middle.
|
//we only accept cache hits that touch either end - no piecing out of the middle.
|
||||||
//this way the amount to be decoded remains set.
|
//this way the amount to be decoded remains set.
|
||||||
if(start < mDecodeCache[i]->start+mDecodeCache[i]->len &&
|
if(start < mDecodeCache[i]->start+mDecodeCache[i]->len &&
|
||||||
start+len > mDecodeCache[i]->start)
|
start + len > mDecodeCache[i]->start)
|
||||||
{
|
{
|
||||||
//ffmpeg only uses 16 bit ints
|
uint8_t* outBuf;
|
||||||
int16_t* outBuf;
|
outBuf = (uint8_t*)data;
|
||||||
outBuf = (int16_t*)data;
|
|
||||||
//reject buffers that would split us into two pieces because we don't have
|
//reject buffers that would split us into two pieces because we don't have
|
||||||
//a method of dealing with this yet, and it won't happen very often.
|
//a method of dealing with this yet, and it won't happen very often.
|
||||||
if(start<mDecodeCache[i]->start && start+len > mDecodeCache[i]->start+mDecodeCache[i]->len)
|
if(start<mDecodeCache[i]->start && start+len > mDecodeCache[i]->start+mDecodeCache[i]->len)
|
||||||
@ -462,9 +477,43 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount &start, sam
|
|||||||
hitStartInCache = FFMAX(0,start-mDecodeCache[i]->start);
|
hitStartInCache = FFMAX(0,start-mDecodeCache[i]->start);
|
||||||
//we also need to find out which end was hit - if it is the tail only we need to update from a later index.
|
//we also need to find out which end was hit - if it is the tail only we need to update from a later index.
|
||||||
hitStartInRequest = start <mDecodeCache[i]->start?len - samplesHit: 0;
|
hitStartInRequest = start <mDecodeCache[i]->start?len - samplesHit: 0;
|
||||||
|
sampleCount outIndex,inIndex;
|
||||||
for(int j=0;j<samplesHit;j++)
|
for(int j=0;j<samplesHit;j++)
|
||||||
{
|
{
|
||||||
outBuf[j+hitStartInRequest]=mDecodeCache[i]->samplePtr[(hitStartInCache+j)*nChannels+channel];
|
outIndex = hitStartInRequest + j;
|
||||||
|
inIndex = (hitStartInCache + j) * nChannels + channel;
|
||||||
|
switch (mDecodeCache[i]->samplefmt)
|
||||||
|
{
|
||||||
|
case SAMPLE_FMT_U8:
|
||||||
|
//printf("u8 in %llu out %llu cachelen %llu outLen %llu\n", inIndex, outIndex, mDecodeCache[i]->len, len);
|
||||||
|
((int16_t *)outBuf)[outIndex] = (int16_t) (((uint8_t*)mDecodeCache[i]->samplePtr)[inIndex] - 0x80) << 8;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SAMPLE_FMT_S16:
|
||||||
|
//printf("u16 in %llu out %llu cachelen %llu outLen %llu\n", inIndex, outIndex, mDecodeCache[i]->len, len);
|
||||||
|
((int16_t *)outBuf)[outIndex] = ((int16_t*)mDecodeCache[i]->samplePtr)[inIndex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SAMPLE_FMT_S32:
|
||||||
|
//printf("s32 in %llu out %llu cachelen %llu outLen %llu\n", inIndex, outIndex, mDecodeCache[i]->len, len);
|
||||||
|
((float *)outBuf)[outIndex] = (float) ((int32_t*)mDecodeCache[i]->samplePtr)[inIndex] * (1.0 / (1 << 31));
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SAMPLE_FMT_FLT:
|
||||||
|
//printf("f in %llu out %llu cachelen %llu outLen %llu\n", inIndex, outIndex, mDecodeCache[i]->len, len);
|
||||||
|
((float *)outBuf)[outIndex] = (float) ((float*)mDecodeCache[i]->samplePtr)[inIndex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
case SAMPLE_FMT_DBL:
|
||||||
|
//printf("dbl in %llu out %llu cachelen %llu outLen %llu\n", inIndex, outIndex, mDecodeCache[i]->len, len);
|
||||||
|
((float *)outBuf)[outIndex] = (float) ((double*)mDecodeCache[i]->samplePtr)[inIndex];
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
printf("ODDecodeFFMPEG TASK unrecognized sample format\n");
|
||||||
|
return 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
//update the cursor
|
//update the cursor
|
||||||
samplesFilled += samplesHit;
|
samplesFilled += samplesHit;
|
||||||
@ -476,7 +525,7 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount &start, sam
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
//we update data pointer too- but it is a typedef'd char* so be careful with the pointer math
|
//we update data pointer too- but it is a typedef'd char* so be careful with the pointer math
|
||||||
data+= samplesHit* (sizeof(int16_t)/sizeof(*data));
|
data+= samplesHit * (SAMPLE_SIZE(outFormat) / sizeof(*data));
|
||||||
start+=samplesHit;
|
start+=samplesHit;
|
||||||
len -=samplesHit;
|
len -=samplesHit;
|
||||||
}
|
}
|
||||||
@ -493,130 +542,15 @@ int ODFFmpegDecoder::FillDataFromCache(samplePtr & data, sampleCount &start, sam
|
|||||||
//get the right stream pointer.
|
//get the right stream pointer.
|
||||||
streamContext* ODFFmpegDecoder::ReadNextFrame()
|
streamContext* ODFFmpegDecoder::ReadNextFrame()
|
||||||
{
|
{
|
||||||
streamContext *sc = NULL;
|
return import_ffmpeg_read_next_frame(mFormatContext, mScs, mNumStreams);
|
||||||
AVPacket pkt;
|
|
||||||
|
|
||||||
if (av_read_frame(mFormatContext,&pkt) < 0)
|
|
||||||
{
|
|
||||||
return NULL;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find a stream to which this frame belongs to
|
|
||||||
for (int i = 0; i < mNumStreams; i++)
|
|
||||||
{
|
|
||||||
if (mScs[i]->m_stream->index == pkt.stream_index)
|
|
||||||
sc = mScs[i];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Off-stream packet. Don't panic, just skip it.
|
|
||||||
// When not all streams are selected for import this will happen very often.
|
|
||||||
if (sc == NULL)
|
|
||||||
{
|
|
||||||
av_free_packet(&pkt);
|
|
||||||
return (streamContext*)1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Copy the frame to the stream context
|
|
||||||
memcpy(&sc->m_pkt, &pkt, sizeof(AVPacket));
|
|
||||||
|
|
||||||
sc->m_pktValid = 1;
|
|
||||||
sc->m_pktDataPtr = pkt.data;
|
|
||||||
sc->m_pktRemainingSiz = pkt.size;
|
|
||||||
|
|
||||||
return sc;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
int ODFFmpegDecoder::DecodeFrame(streamContext *sc, bool flushing)
|
int ODFFmpegDecoder::DecodeFrame(streamContext *sc, bool flushing)
|
||||||
{
|
{
|
||||||
int nBytesDecoded;
|
int ret = import_ffmpeg_decode_frame(sc, flushing);
|
||||||
wxUint8 *pDecode = sc->m_pktDataPtr;
|
|
||||||
int nDecodeSiz = sc->m_pktRemainingSiz;
|
|
||||||
|
|
||||||
//check to see if the sc has already been decoded in our sample range
|
if (ret == 0 && sc->m_frameValid) {
|
||||||
|
|
||||||
sc->m_frameValid = 0;
|
|
||||||
|
|
||||||
if (flushing)
|
|
||||||
{
|
|
||||||
// If we're flushing the decoders we don't actually have any new data to decode.
|
|
||||||
pDecode = NULL;
|
|
||||||
nDecodeSiz = 0;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
if (!sc->m_pktValid || (sc->m_pktRemainingSiz <= 0))
|
|
||||||
{
|
|
||||||
//No more data
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Reallocate the audio sample buffer if it's smaller than the frame size.
|
|
||||||
if (!flushing)
|
|
||||||
{
|
|
||||||
// av_fast_realloc() will only reallocate the buffer if m_decodedAudioSamplesSiz is
|
|
||||||
// smaller than third parameter. It also returns new size in m_decodedAudioSamplesSiz
|
|
||||||
//\warning { for some reason using the following macro call right in the function call
|
|
||||||
// causes Audacity to crash in some unknown place. With "newsize" it works fine }
|
|
||||||
int newsize = FFMAX(sc->m_pkt.size*sizeof(*sc->m_decodedAudioSamples), AVCODEC_MAX_AUDIO_FRAME_SIZE);
|
|
||||||
sc->m_decodedAudioSamples = (uint8_t*)av_fast_realloc(sc->m_decodedAudioSamples,
|
|
||||||
&sc->m_decodedAudioSamplesSiz,
|
|
||||||
newsize
|
|
||||||
);
|
|
||||||
|
|
||||||
if (sc->m_decodedAudioSamples == NULL)
|
|
||||||
{
|
|
||||||
//Can't allocate bytes
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// avcodec_decode_audio2() expects the size of the output buffer as the 3rd parameter but
|
|
||||||
// also returns the number of bytes it decoded in the same parameter.
|
|
||||||
sc->m_decodedAudioSamplesValidSiz = sc->m_decodedAudioSamplesSiz;
|
|
||||||
|
|
||||||
#if LIBAVCODEC_VERSION_INT > AV_VERSION_INT(52, 25, 0)
|
|
||||||
// avcodec_decode_audio3() expects the size of the output buffer as the 3rd parameter but
|
|
||||||
// also returns the number of bytes it decoded in the same parameter.
|
|
||||||
AVPacket avpkt;
|
|
||||||
av_init_packet(&avpkt);
|
|
||||||
avpkt.data = pDecode;
|
|
||||||
avpkt.size = nDecodeSiz;
|
|
||||||
nBytesDecoded =
|
|
||||||
avcodec_decode_audio3(sc->m_codecCtx,
|
|
||||||
(int16_t *)sc->m_decodedAudioSamples, // out
|
|
||||||
&sc->m_decodedAudioSamplesValidSiz, // in/out
|
|
||||||
&avpkt); // in
|
|
||||||
#else
|
|
||||||
// avcodec_decode_audio2() expects the size of the output buffer as the 3rd parameter but
|
|
||||||
// also returns the number of bytes it decoded in the same parameter.
|
|
||||||
nBytesDecoded =
|
|
||||||
avcodec_decode_audio2(sc->m_codecCtx,
|
|
||||||
(int16_t *) sc->m_decodedAudioSamples, // out
|
|
||||||
&sc->m_decodedAudioSamplesValidSiz, // in/out
|
|
||||||
pDecode, // in
|
|
||||||
nDecodeSiz); // in
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (nBytesDecoded < 0)
|
|
||||||
{
|
|
||||||
// Decoding failed. Don't stop.
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We may not have read all of the data from this packet. If so, the user can call again.
|
|
||||||
// Whether or not they do depends on if m_pktRemainingSiz == 0 (they can check).
|
|
||||||
sc->m_pktDataPtr += nBytesDecoded;
|
|
||||||
sc->m_pktRemainingSiz -= nBytesDecoded;
|
|
||||||
|
|
||||||
// At this point it's normally safe to assume that we've read some samples. However, the MPEG
|
|
||||||
// audio decoder is broken. If this is the case then we just return with m_frameValid == 0
|
|
||||||
// but m_pktRemainingSiz perhaps != 0, so the user can call again.
|
|
||||||
if (sc->m_decodedAudioSamplesValidSiz > 0)
|
|
||||||
{
|
|
||||||
sc->m_frameValid = 1;
|
|
||||||
|
|
||||||
//stick it in the cache.
|
//stick it in the cache.
|
||||||
//TODO- consider growing/unioning a few cache buffers like WaveCache does.
|
//TODO- consider growing/unioning a few cache buffers like WaveCache does.
|
||||||
//however we can't use wavecache as it isn't going to handle our stereo interleaved part, and isn't for samples
|
//however we can't use wavecache as it isn't going to handle our stereo interleaved part, and isn't for samples
|
||||||
@ -624,11 +558,12 @@ int ODFFmpegDecoder::DecodeFrame(streamContext *sc, bool flushing)
|
|||||||
FFMpegDecodeCache* cache = new FFMpegDecodeCache;
|
FFMpegDecodeCache* cache = new FFMpegDecodeCache;
|
||||||
//len is number of samples per channel
|
//len is number of samples per channel
|
||||||
cache->numChannels = sc->m_stream->codec->channels;
|
cache->numChannels = sc->m_stream->codec->channels;
|
||||||
// Here we convert to 16 bit stero frame length
|
|
||||||
cache->len = (sc->m_decodedAudioSamplesValidSiz/sizeof(int16_t) ) / cache->numChannels;
|
cache->len = (sc->m_decodedAudioSamplesValidSiz / sc->m_samplesize) / cache->numChannels;
|
||||||
cache->start=mCurrentPos;
|
cache->start = mCurrentPos;
|
||||||
cache->samplePtr = (int16_t*) malloc(sc->m_decodedAudioSamplesValidSiz);
|
cache->samplePtr = (uint8_t*) malloc(sc->m_decodedAudioSamplesValidSiz);
|
||||||
memcpy(cache->samplePtr,sc->m_decodedAudioSamples,sc->m_decodedAudioSamplesValidSiz);
|
cache->samplefmt = sc->m_samplefmt;
|
||||||
|
memcpy(cache->samplePtr, sc->m_decodedAudioSamples, sc->m_decodedAudioSamplesValidSiz);
|
||||||
|
|
||||||
InsertCache(cache);
|
InsertCache(cache);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user