123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417 |
- #include "main.h"
- #include "../winamp/wa_ipc.h"
- #include "VideoThread.h"
- #include "AudioSample.h"
- #include "api__in_mp4.h"
- #include <assert.h>
- #include <api/service/waservicefactory.h>
- #include "../nu/AudioOutput.h"
- #include <strsafe.h>
- const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused
- static bool audio_opened;
- static HANDLE events[3];
- static bool done;
- bool open_mp4(const wchar_t *fn);
- static AudioSample *sample = 0;
- static DWORD waitTime;
- static MP4SampleId nextSampleId;
- Nullsoft::Utility::LockGuard play_mp4_guard;
- static MP4Duration first_timestamp=0;
- class MP4Wait
- {
- public:
- int WaitOrAbort(int time_in_ms)
- {
- WaitForMultipleObjects(3, events, FALSE, INFINITE); // pauseEvent signal state is opposite of pause state
- int ret = WaitForMultipleObjects(2, events, FALSE, time_in_ms);
- if (ret == WAIT_TIMEOUT)
- return 0;
- if (ret == WAIT_OBJECT_0+1)
- return 2;
- return 1;
- }
- };
- nu::AudioOutput<MP4Wait> audio_output(&mod);
- MP4Duration GetClock()
- {
- if (audio)
- {
- return audio_output.GetFirstTimestamp() + mod.outMod->GetOutputTime();
- }
- else if (video)
- {
- return video_clock.GetOutputTime();
- }
- else
- {
- return 0;
- }
- }
- static void OutputSample(AudioSample *sample)
- {
- if (first)
- {
- first_timestamp = MP4ConvertFromTrackTimestamp(MP4hFile, audio_track, sample->timestamp, MP4_MSECS_TIME_SCALE);
- first = false;
- }
- if (sample->result == MP4_SUCCESS)
- {
- if (!audio_opened)
- {
- audio_opened=true;
- if (audio_output.Open(first_timestamp, sample->numChannels, sample->sampleRate, sample->bitsPerSample) == false)
- {
- PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
- return ;
- }
- unsigned __int32 pregap = 0;
- unsigned __int32 postgap = 0;
- GetGaps(MP4hFile, pregap, postgap);
- audio_output.SetDelays(0, pregap, postgap);
- mod.SetInfo(audio_bitrate + video_bitrate, sample->sampleRate / 1000, sample->numChannels, 1);
- }
- int skip = 0;
- int sample_size = (sample->bitsPerSample / 8) * sample->numChannels;
- int outSamples = MulDiv(sample->outputValid, m_timescale, sample->sampleRate * sample_size);
- /* if (!audio_chunk && outSamples > sample->duration)
- outSamples = (int)sample->duration; */
- if (sample->offset > 0)
- {
- int cut = (int)min(outSamples, sample->offset);
- outSamples -= cut;
- skip = cut;
- }
- size_t outSize = MulDiv(sample_size * sample->sampleRate, outSamples, m_timescale);
- if (audio_bitrate != sample->bitrate)
- {
- audio_bitrate = sample->bitrate;
- mod.SetInfo(audio_bitrate + video_bitrate, -1, -1, 1);
- }
- if (audio_output.Write(sample->output + MulDiv(sample_size * sample->sampleRate, skip, m_timescale), outSize) == 1)
- {
- return ;
- }
- if (sample->sampleId == numSamples) // done!
- done = true; // TODO: probably don't want to bail out yet if video is playing
- }
- }
- static bool DecodeAudioSample(AudioSample *sample)
- {
- if (m_needseek != -1)
- {
- sample->outputValid = 0;
- sample->outputCursor = sample->output;
- sample->result = MP4_SUCCESS;
- sample->sampleRate = m_timescale;
- audio->GetOutputProperties(&sample->sampleRate, &sample->numChannels, &sample->bitsPerSample);
- if (audio->GetCurrentBitrate(&sample->bitrate) != MP4_SUCCESS || !sample->bitrate)
- sample->bitrate = audio_bitrate;
- }
- else
- {
- sample->outputValid = sample->outputSize;
- sample->outputCursor = 0;
- sample->result = audio->DecodeSample(sample->input, sample->inputValid, sample->output, &sample->outputValid);
- if (sample->inputValid == 0 && sample->outputValid == 0) {
- return false;
- }
- sample->sampleRate = m_timescale;
- audio->GetOutputProperties(&sample->sampleRate, &sample->numChannels, &sample->bitsPerSample);
- if (audio->GetCurrentBitrate(&sample->bitrate) != MP4_SUCCESS || !sample->bitrate)
- sample->bitrate = audio_bitrate;
- OutputSample(sample);
- }
- return true;
- }
- static void ReadNextAudioSample()
- {
- if (nextSampleId > numSamples)
- {
- return;
- }
- unsigned __int32 buffer_size = sample->inputSize;
- bool sample_read = false;
- play_mp4_guard.Lock();
- if (audio_chunk)
- sample_read = MP4ReadChunk(MP4hFile, audio_track, nextSampleId++, (unsigned __int8 **)&sample->input, &buffer_size, &sample->timestamp, &sample->duration);
- else
- sample_read = MP4ReadSample(MP4hFile, audio_track, nextSampleId++, (unsigned __int8 **)&sample->input, &buffer_size, &sample->timestamp, &sample->duration, &sample->offset);
- play_mp4_guard.Unlock();
- if (sample_read)
- {
- sample->inputValid = buffer_size;
- if (audio_chunk)
- {
- sample->duration = 0;
- sample->offset = 0;
- }
- sample->sampleId = nextSampleId-1;
- DecodeAudioSample(sample);
- }
- }
- static bool BuildAudioBuffers()
- {
- size_t outputFrameSize;
- //if (audio->OutputFrameSize(&outputFrameSize) != MP4_SUCCESS || !outputFrameSize)
- //{
- outputFrameSize = 8192 * 6; // fallback size
- //}
- u_int32_t maxSize = 0;
- if (audio)
- {
- if (audio_chunk)
- maxSize = 65536; // TODO!!!!
- else
- maxSize = MP4GetTrackMaxSampleSize(MP4hFile, audio_track);
- if (!maxSize)
- return 0;
- sample = new AudioSample(maxSize, outputFrameSize);
- if (!sample->OK())
- {
- delete sample;
- return false;
- }
- }
- if (video)
- {
- maxSize = MP4GetTrackMaxSampleSize(MP4hFile, video_track);
- video_sample = new VideoSample(maxSize);
- if (!video_sample->OK())
- {
- delete video_sample;
- return false;
- }
- }
- return true;
- }
- DWORD WINAPI PlayProc(LPVOID lpParameter)
- {
- // set an event when we start. this keeps Windows from queueing an APC before the thread proc even starts (evil, evil windows)
- HANDLE threadCreatedEvent = (HANDLE)lpParameter;
- SetEvent(threadCreatedEvent);
- video=0;
- if (!open_mp4(lastfn))
- {
- if (WaitForSingleObject(killEvent, 200) != WAIT_OBJECT_0)
- PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
- return 0;
- }
- audio_output.Init(mod.outMod);
- if (videoOutput && video)
- {
- // TODO: this is really just a placeholder, we should do smarter stuff
- // like query the decoder object for a name rather than guess
- char set_info[256] = {0};
- char *audio_info = MP4PrintAudioInfo(MP4hFile, audio_track);
- char *video_info = 0;
- if (video_track != MP4_INVALID_TRACK_ID)
- video_info = MP4PrintVideoInfo(MP4hFile, video_track);
- if (video_info)
- {
- StringCchPrintfA(set_info, 256, "%s, %s %ux%u", audio_info, video_info, MP4GetTrackVideoWidth(MP4hFile, video_track), MP4GetTrackVideoHeight(MP4hFile, video_track));
- videoOutput->extended(VIDUSER_SET_INFOSTRING,(INT_PTR)set_info,0);
- MP4Free(video_info);
- }
- MP4Free(audio_info);
- }
- if (!BuildAudioBuffers())
- {
- // TODO: benski> more cleanup work has to be done here!
- if (WaitForSingleObject(killEvent, 200) != WAIT_OBJECT_0)
- PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
- return 0;
- }
- nextSampleId = 1;
- nextVideoSampleId = 1;
- if (video)
- Video_Init();
- first = true;
- audio_opened = false;
- first_timestamp= 0;
- events[0]=killEvent;
- events[1]=seekEvent;
- events[2]=pauseEvent;
- waitTime = audio?0:INFINITE;
- done = false;
- while (!done)
- {
- int ret = WaitForMultipleObjects(2, events, FALSE, waitTime);
- switch (ret)
- {
- case WAIT_OBJECT_0: // kill event
- done = true;
- break;
- case WAIT_OBJECT_0 + 1: // seek event
- {
- bool rewind = m_needseek < GetClock();
- // TODO: reset pregap?
- MP4SampleId new_video_sample = MP4_INVALID_SAMPLE_ID;
- if (video)
- {
- SetEvent(video_start_flushing);
- WaitForSingleObject(video_flush_done, INFINITE);
- MP4Duration duration = MP4ConvertToTrackDuration(MP4hFile, video_track, m_needseek, MP4_MSECS_TIME_SCALE);
- if (duration != MP4_INVALID_DURATION)
- {
- new_video_sample = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, true, rewind);
- if (new_video_sample == MP4_INVALID_SAMPLE_ID)
- new_video_sample = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, false); // try again without keyframe seeking
- /* TODO: make sure the new seek direction is in the same as the request seek direction.
- e.g. make sure a seek FORWARD doesn't go BACKWARD
- MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, seek_video_sample);
- int new_time = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE);
- if (m_needseek < GetClock())
- video_timestamp = MP4GetSampleIdFromTime(MP4hFile, video_track, duration, true); // first closest keyframe prior
- */
- if (new_video_sample != MP4_INVALID_SAMPLE_ID)
- {
- int m_old_needseek = m_needseek;
- MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, new_video_sample);
- m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE);
- if (!audio)
- {
- MP4Timestamp video_timestamp = MP4GetSampleTime(MP4hFile, video_track, new_video_sample);
- m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, video_track, video_timestamp, MP4_MSECS_TIME_SCALE);
- video_clock.Seek(m_needseek);
- m_needseek = -1;
- }
- else
- {
- // TODO check this will just do what is needed
- // aim of this is when there is 1 artwork
- // frame then we don't lock audio<->video
- // as it otherwise prevents audio seeking
- if (!m_needseek && m_old_needseek != m_needseek && new_video_sample == 1)
- {
- m_needseek = m_old_needseek;
- }
- }
- }
- }
- }
- if (audio)
- {
- MP4Duration duration = MP4ConvertToTrackDuration(MP4hFile, audio_track, m_needseek, MP4_MSECS_TIME_SCALE);
- if (duration != MP4_INVALID_DURATION)
- {
- MP4SampleId newSampleId = audio_chunk?MP4GetChunkIdFromTime(MP4hFile, audio_track, duration):MP4GetSampleIdFromTime(MP4hFile, audio_track, duration);
- if (newSampleId != MP4_INVALID_SAMPLE_ID)
- {
- audio->Flush();
- if (video)
- {
- if (new_video_sample == MP4_INVALID_SAMPLE_ID)
- {
- SetEvent(video_resume);
- }
- else
- {
- nextVideoSampleId = new_video_sample;
- SetEvent(video_flush);
- }
- WaitForSingleObject(video_flush_done, INFINITE);
- }
- m_needseek = MP4ConvertFromTrackTimestamp(MP4hFile, audio_track, duration, MP4_MILLISECONDS_TIME_SCALE);
- ResetEvent(seekEvent);
- audio_output.Flush(m_needseek);
- m_needseek = -1;
- nextSampleId = newSampleId;
- continue;
- }
- }
- }
- else
- {
- if (new_video_sample == MP4_INVALID_SAMPLE_ID)
- {
- SetEvent(video_resume);
- }
- else
- {
- nextVideoSampleId = new_video_sample;
- SetEvent(video_flush);
- }
- WaitForSingleObject(video_flush_done, INFINITE);
- ResetEvent(seekEvent);
- continue;
- }
- }
- break;
- case WAIT_TIMEOUT:
- ReadNextAudioSample();
- break;
- }
- }
- if (WaitForSingleObject(killEvent, 0) == WAIT_TIMEOUT) // if (!killed)
- {
- // tell audio decoder about end-of-stream and get remaining audio
- /* if (audio) {
- audio->EndOfStream();
-
- sample->inputValid = 0;
- while (DecodeAudioSample(sample)) {
- }
- }
- */
- audio_output.Write(0,0);
- audio_output.WaitWhilePlaying();
- if (WaitForSingleObject(killEvent, 0) == WAIT_TIMEOUT)
- PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
- }
- SetEvent(killEvent);
- // eat the rest of the APC messages
- while (SleepEx(0, TRUE) == WAIT_IO_COMPLETION) {}
- if (video)
- Video_Close();
- return 0;
- }
|