123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388 |
- #pragma once
- #include <bfc/platform/types.h>
- #include "../Winamp/in2.h"
- #include "../Winamp/out.h"
- #include "SpillBuffer.h"
- #include <assert.h>
- /* A class to manage Winamp input plugin audio output
- ** It handles the following for you:
- ** * Ensuring that Vis data is sent in chunks of 576
- ** * Dealing with gapless audio
- ** (you need to pass in the number of pre-delay and post-delay samples)
- ** * dealing with the DSP plugin
- ** * Waiting for CanWrite()
- ** * dealing with inter-timestamps
- ** e.g. you pass it >576 samples and it can give you a timestamp based on the divided chunk position
- to use, you need to derive from a class that declares
- int WaitOrAbort(int time_in_ms);
- return 0 on success, non-zero when you need to abort. the return value is passed back through Write()
- */
- namespace nu // namespace it since "AudioOutput" isn't a unique enough name
- {
- template <class wait_t>
- class AudioOutput : public wait_t
- {
- public:
- AudioOutput( In_Module *plugin ) : plugin( plugin )
- {
- Init( nullptr );
- }
- ~AudioOutput()
- {
- post_buffer.reset();
- buffer576.reset();
- }
- /* Initializes and sets the output plugin pointer
- ** for most input plugins, the nu::AudioOutput object will be a global,
- ** so this will be necessary to call at the start of Play thread */
- void Init( Out_Module *_output )
- {
- output = _output;
- audio_opened = false;
- first_timestamp = 0;
- sample_size = 0;
- output_latency = 0;
- post_buffer.reset();
- buffer576.reset();
- cut_size = 0;
- pre_cut_size = 0;
- pre_cut = 0;
- decoder_delay = 0;
- channels = 0;
- sample_rate = 0;
- bps = 0;
- }
- /* sets end-of-stream delay (in samples)
- ** WITHOUT componesating for post-delay.
- ** some filetypes (e.g. iTunes MP4) store gapless info this way */
- void SetPostDelay(int postSize)
- {
- if (postSize < 0)
- {
- postSize = 0;
- }
- else if (postSize)
- {
- if (sample_size)
- post_buffer.reserve(postSize*sample_size);
- cut_size = postSize;
- }
- }
- /* set end-of-stream zero padding, in samples
- ** compensates for decoder delay */
- void SetZeroPadding(int postSize)
- {
- postSize -= decoder_delay;
- if (postSize < 0)
- {
- postSize = 0;
- }
- SetPostDelay(postSize);
- }
- /* set decoder delay, initial zero samples and end-of-stream zero samples, all in one shot
- ** adjusts zero samples for decoder delay. call SetDelays() if your zero samples are already compensated */
- void SetGapless(int decoderDelaySize, int preSize, int postSize)
- {
- decoder_delay = decoderDelaySize;
- SetZeroPadding(postSize);
- pre_cut_size = preSize;
- pre_cut = pre_cut_size + decoder_delay;
- }
- /* set decoder delay, initial delay and end-of-stream delay, all in one shot
- ** WITHOUT componesating for post-delay.
- ** some filetypes (e.g. iTunes MP4) store gapless info this way */
- void SetDelays(int decoderDelaySize, int preSize, int postSize)
- {
- decoder_delay = decoderDelaySize;
- SetPostDelay(postSize);
- pre_cut_size = preSize;
- pre_cut = pre_cut_size;
- }
- /* Call on seek */
- void Flush(int time_in_ms)
- {
- if (audio_opened)
- {
- pre_cut = pre_cut_size;
- output->Flush(time_in_ms);
- first_timestamp = 0; // once we've flushed, we should be accurate so no need for this anymore
- buffer576.clear();
- post_buffer.clear();
- }
- else
- first_timestamp = time_in_ms;
- }
- bool Opened() const
- {
- return audio_opened;
- }
- int GetLatency() const
- {
- return output_latency;
- }
- int GetFirstTimestamp() const
- {
- return first_timestamp;
- }
- /* timestamp is meant to be the first timestamp according to the containing file format
- ** e.g. many MP4 videos start on 12ms or something, for accurate a/v syncing */
- bool Open(int timestamp, int channels, int sample_rate, int bps, int buffer_len_ms=-1, int pre_buffer_ms=-1)
- {
- if (!audio_opened)
- {
- int latency = output->Open(sample_rate, channels, bps, buffer_len_ms, pre_buffer_ms);
- if (latency < 0)
- return false;
- plugin->SAVSAInit(latency, sample_rate);
- plugin->VSASetInfo(sample_rate, channels);
- output->SetVolume(-666);
- plugin->SetInfo(-1, sample_rate / 1000, channels, /* TODO? 0*/1);
- output_latency = latency;
- first_timestamp = timestamp;
- sample_size = channels*bps / 8;
- this->channels=channels;
- this->sample_rate=sample_rate;
- this->bps=bps;
- SetPostDelay((int)cut_size); // set this again now that we know sample_size, so buffers get allocated correctly
- buffer576.reserve(576*sample_size);
- audio_opened=true;
- }
- return audio_opened;
- }
- void Close()
- {
- if (audio_opened && output)
- {
- output->Close();
- plugin->SAVSADeInit();
- }
- output = 0;
- first_timestamp = 0;
- }
- /* outSize is in bytes
- ** */
- int Write(char *out, size_t outSize)
- {
- if (!out && !outSize)
- {
- /* --- write contents of buffered audio (end-zero-padding buffer) */
- if (!post_buffer.empty())
- {
- void *buffer = 0;
- size_t len = 0;
- if (post_buffer.get(&buffer, &len))
- {
- int ret = Write576((char *)buffer, len);
- if (ret != 0)
- return ret;
- }
- }
- /* --- write any remaining data in 576 spill buffer (skip vis) */
- if (!buffer576.empty())
- {
- void *buffer = 0;
- size_t len = 0;
- if (buffer576.get(&buffer, &len))
- {
- int ret = WriteOutput((char *)buffer, len);
- if (ret != 0)
- return ret;
- }
- }
- output->Write(0, 0);
- return 0;
- }
- // this probably should not happen but have seen it in some crash reports
- if (!sample_size)
- return 0;
- assert((outSize % sample_size) == 0);
- size_t outSamples = outSize / sample_size;
- /* --- cut pre samples, if necessary --- */
- size_t pre = min(pre_cut, outSamples);
- out += pre * sample_size;
- outSize -= pre * sample_size;
- pre_cut -= pre;
- //outSize = outSamples * sample_size;
- // do we will have samples to output after cutting pre-delay?
- if (!outSize)
- return 0;
- /* --- if we don't have enough to fully fill the end-zero-padding buffer, go ahead and fill --- */
- if (outSize < post_buffer.length())
- {
- size_t bytes_written = post_buffer.write(out, outSize);
- out+=bytes_written;
- outSize-=bytes_written;
- }
- // if we're out of samples, go ahead and bail
- if (!outSize)
- return 0;
- /* --- write contents of buffered audio (end-zero-padding buffer) */
- if (!post_buffer.empty())
- {
- void *buffer = 0;
- size_t len = 0;
- if (post_buffer.get(&buffer, &len))
- {
- int ret = Write576((char *)buffer, len);
- if (ret != 0)
- return ret;
- }
- }
- /* --- make sure we have enough samples left over to fill our post-zero-padding buffer --- */
- size_t remainingFill = /*cut_size - */post_buffer.remaining();
- int outWrite = max(0, (int)outSize - (int)remainingFill);
- /* --- write the output that doesn't end up in the post buffer */
- if (outWrite)
- {
- int ret = Write576(out, outWrite);
- if (ret != 0)
- return ret;
- }
- out += outWrite;
- outSize -= outWrite;
- /* --- write whatever is left over into the end-zero-padding buffer --- */
- if (outSize)
- {
- post_buffer.write(out, outSize);
- }
- return 0;
- }
- /* meant to be called after Write(0,0) */
- int WaitWhilePlaying()
- {
- while (output->IsPlaying())
- {
- int ret = WaitOrAbort(10);
- if (ret != 0)
- return ret;
- output->CanWrite(); // some output drivers need CanWrite
- // to be called on a regular basis.
- }
- return 0;
- }
- private:
- /* helper methods */
- int WaitForOutput(int write_size_bytes)
- {
- while (output->CanWrite() < write_size_bytes)
- {
- int ret = WaitOrAbort(55);
- if (ret != 0)
- return ret;
- }
- return 0;
- }
- /* writes one chunk (576 samples) to the output plugin, waiting as necessary */
- int WriteOutput(char *buffer, size_t len)
- {
- int ret = WaitForOutput((int)len);
- if (ret != 0)
- return ret;
- // write vis data before so we guarantee 576 samples
- if (len == 576*sample_size)
- {
- plugin->SAAddPCMData(buffer, channels, bps, output->GetWrittenTime() + first_timestamp);
- plugin->VSAAddPCMData(buffer, channels, bps, output->GetWrittenTime() + first_timestamp);
- }
- if (plugin->dsp_isactive())
- len = sample_size * plugin->dsp_dosamples((short *)buffer, (int)(len / sample_size), bps, channels, sample_rate);
- output->Write(buffer, (int)len);
- return 0;
- }
- /* given a large buffer, writes 576 sample chunks to the vis, dsp and output plugin */
- int Write576(char *buffer, size_t out_size)
- {
- /* if we have some stuff leftover in the 576 sample spill buffer, fill it up */
- if (!buffer576.empty())
- {
- size_t bytes_written = buffer576.write(buffer, out_size);
- out_size -= bytes_written;
- buffer += bytes_written;
- }
- if (buffer576.full())
- {
- void *buffer = 0;
- size_t len = 0;
- if (buffer576.get(&buffer, &len))
- {
- int ret = WriteOutput((char *)buffer, len);
- if (ret != 0)
- return ret;
- }
- }
- while (out_size >= 576*sample_size)
- {
- int ret = WriteOutput(buffer, 576*sample_size);
- if (ret != 0)
- return ret;
- out_size -= 576*sample_size;
- buffer+=576*sample_size;
- }
- if (out_size)
- {
- assert(out_size < 576*sample_size);
- buffer576.write(buffer, out_size);
- }
- return 0;
- }
- private:
- Out_Module *output;
- In_Module *plugin;
- SpillBuffer post_buffer, buffer576;
- size_t cut_size;
- size_t pre_cut, pre_cut_size, decoder_delay;
- bool audio_opened;
- int first_timestamp; /* timestamp of the first decoded audio frame, necessary for accurate video syncing */
- size_t sample_size; /* size, in bytes, of one sample of audio (channels*bps/8) */
- int output_latency; /* as returned from Out_Module::Open() */
- int channels, sample_rate, bps;
- };
- }
|