#include "main.h" #include "../winamp/wa_ipc.h" #include "VideoThread.h" #include "AudioSample.h" #include "api__in_mp4.h" #include #include #include "../nu/AudioOutput.h" #include 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 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; }