diff --git a/src/apps/mediaplayer/Controller.cpp b/src/apps/mediaplayer/Controller.cpp index c37b1f4c6e..cc3fa216dd 100644 --- a/src/apps/mediaplayer/Controller.cpp +++ b/src/apps/mediaplayer/Controller.cpp @@ -2,7 +2,7 @@ * Controller.cpp - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen - * Copyright (C) 2007 Stephan Aßmus + * Copyright (C) 2007-2008 Stephan Aßmus (MIT Ok) * Copyright (C) 2007 Fredrik Modéen * * This program is free software; you can redistribute it and/or @@ -33,25 +33,22 @@ #include #include // for debugging only +#include "AutoDeleter.h" #include "ControllerView.h" #include "PlaybackState.h" -#include "SoundOutput.h" #include "VideoView.h" // suppliers -#include "AudioSupplier.h" +#include "AudioTrackSupplier.h" #include "MediaTrackAudioSupplier.h" #include "MediaTrackVideoSupplier.h" -#include "VideoSupplier.h" +#include "ProxyAudioSupplier.h" +#include "ProxyVideoSupplier.h" +#include "VideoTrackSupplier.h" using std::nothrow; -#define AUDIO_PLAY_PRIORITY 110 -#define VIDEO_PLAY_PRIORITY 20 -#define AUDIO_DECODE_PRIORITY 13 -#define VIDEO_DECODE_PRIORITY 13 - void HandleError(const char *text, status_t err) { @@ -63,7 +60,7 @@ HandleError(const char *text, status_t err) } -// #pragma mark - +// #pragma mark - Controller::Listener Controller::Listener::Listener() {} @@ -80,114 +77,76 @@ void Controller::Listener::VolumeChanged(float) {} void Controller::Listener::MutedChanged(bool) {} -// #pragma mark - +// #pragma mark - Controller Controller::Controller() - : fVideoView(NULL) - , fPaused(false) - , fStopped(true) + : NodeManager() + , fVideoView(NULL) , fVolume(1.0) , fMuted(false) , fRef() - , fMediaFile(0) - , fDataLock("controller data lock") + , fMediaFile(NULL) - , fVideoSupplier(NULL) - , fAudioSupplier(NULL) + , fVideoSupplier(new ProxyVideoSupplier()) + , fAudioSupplier(new ProxyAudioSupplier(this)) + , fVideoTrackSupplier(NULL) + , fAudioTrackSupplier(NULL) - , fVideoSupplierLock("video supplier lock") - , fAudioSupplierLock("audio supplier lock") + , fAudioTrackList(4) + , fVideoTrackList(2) - , fAudioTrackList(new BList) - , fVideoTrackList(new BList) - , fAudioDecodeSem(-1) - , fVideoDecodeSem(-1) - , fAudioPlaySem(-1) - , fVideoPlaySem(-1) - , fAudioDecodeThread(-1) - , fVideoDecodeThread(-1) - , fAudioPlayThread(-1) - , fVideoPlayThread(-1) - , fSoundOutput(NULL) - , fSeekAudio(false) - , fSeekVideo(false) - , fSeekPosition(0) , fPosition(0) , fDuration(0) - , fAudioBufferCount(MAX_AUDIO_BUFFERS) - , fAudioBufferReadIndex(0) - , fAudioBufferWriteIndex(0) - , fVideoBufferCount(MAX_VIDEO_BUFFERS) - , fVideoBufferReadIndex(0) - , fVideoBufferWriteIndex(0) - , fTimeSourceLock("time source lock") - , fTimeSourceSysTime(0) - , fTimeSourcePerfTime(0) + , fVideoFrameRate(25.0) + , fAutoplay(true) , fPauseAtEndOfStream(false) , fSeekToStartAfterPause(false) - , fCurrentBitmap(NULL) - , fBitmapLock("bitmap lock") + , fListeners(4) { - for (int i = 0; i < MAX_AUDIO_BUFFERS; i++) { - fAudioBuffer[i].bitmap = NULL; - fAudioBuffer[i].buffer = NULL; - fAudioBuffer[i].sizeMax = 0; - fAudioBuffer[i].formatChanged = false; - fAudioBuffer[i].endOfStream = false; - } - for (int i = 0; i < MAX_VIDEO_BUFFERS; i++) { - fVideoBuffer[i].bitmap = NULL; - fVideoBuffer[i].buffer = NULL; - fVideoBuffer[i].sizeMax = 0; - fVideoBuffer[i].formatChanged = false; - fVideoBuffer[i].endOfStream = false; - } - fStopped = fAutoplay ? false : true; - - _StartThreads(); } Controller::~Controller() { - _StopThreads(); - if (fMediaFile) fMediaFile->ReleaseAllTracks(); delete fMediaFile; - delete fAudioTrackList; - delete fVideoTrackList; - - delete fSoundOutput; } -// #pragma mark - +// #pragma mark - NodeManager interface -bool -Controller::Lock() +int64 +Controller::Duration() { - return fDataLock.Lock(); + return (int64)((double)fDuration * fVideoFrameRate / 1000000.0); } -status_t -Controller::LockWithTimeout(bigtime_t timeout) +VideoTarget* +Controller::CreateVideoTarget() { - return fDataLock.LockWithTimeout(timeout); + return fVideoView; } -void -Controller::Unlock() +VideoSupplier* +Controller::CreateVideoSupplier() { - return fDataLock.Unlock(); + return fVideoSupplier; +} + + +AudioSupplier* +Controller::CreateAudioSupplier() +{ + return fAudioSupplier; } @@ -197,46 +156,49 @@ Controller::Unlock() status_t Controller::SetTo(const entry_ref &ref) { - BAutolock dl(fDataLock); - BAutolock al(fVideoSupplierLock); - BAutolock vl(fAudioSupplierLock); + BAutolock _(this); - if (fRef == ref) + if (fRef == ref) { + SetCurrentFrame(0); + StartPlaying(); return B_OK; + } fRef = ref; - fAudioTrackList->MakeEmpty(); - fVideoTrackList->MakeEmpty(); - if (fMediaFile) - fMediaFile->ReleaseAllTracks(); - delete fMediaFile; - fMediaFile = NULL; - delete fVideoSupplier; - fVideoSupplier = NULL; - delete fAudioSupplier; - fAudioSupplier = NULL; + fAudioSupplier->SetSupplier(NULL, fVideoFrameRate); + fVideoSupplier->SetSupplier(NULL); + + fAudioTrackList.MakeEmpty(); + fVideoTrackList.MakeEmpty(); - fSeekAudio = true; - fSeekVideo = true; - fSeekPosition = 0; - fPaused = false; - fStopped = fAutoplay ? false : true; + ObjectDeleter oldMediaFileDeleter(fMediaFile); + // BMediaFile destructor will call ReleaseAllTracks() + fMediaFile = NULL; + + // Do not delete the supplier chain until after we called + // NodeManager::Init() to setup a new media node chain + ObjectDeleter videoSupplierDeleter( + fVideoTrackSupplier); + ObjectDeleter audioSupplierDeleter( + fAudioTrackSupplier); + + fVideoTrackSupplier = NULL; + fAudioTrackSupplier = NULL; + fPauseAtEndOfStream = false; fSeekToStartAfterPause = false; - fTimeSourceSysTime = system_time(); - fTimeSourcePerfTime = 0; fDuration = 0; - - _UpdatePosition(0); + fVideoFrameRate = 25.0; status_t err; BMediaFile *mf = new BMediaFile(&ref); + ObjectDeleter mediaFileDeleter(mf); + err = mf->InitCheck(); if (err != B_OK) { printf("Controller::SetTo: initcheck failed\n"); - delete mf; _NotifyFileChanged(); return err; } @@ -244,7 +206,6 @@ Controller::SetTo(const entry_ref &ref) int trackcount = mf->CountTracks(); if (trackcount <= 0) { printf("Controller::SetTo: trackcount %d\n", trackcount); - delete mf; _NotifyFileChanged(); return B_MEDIA_NO_HANDLER; } @@ -260,9 +221,9 @@ Controller::SetTo(const entry_ref &ref) continue; } if (f.IsAudio()) { - fAudioTrackList->AddItem(t); + fAudioTrackList.AddItem(t); } else if (f.IsVideo()) { - fVideoTrackList->AddItem(t); + fVideoTrackList.AddItem(t); } else { printf("Controller::SetTo: track index %d has unknown type\n", i); mf->ReleaseTrack(t); @@ -271,20 +232,34 @@ Controller::SetTo(const entry_ref &ref) if (AudioTrackCount() == 0 && VideoTrackCount() == 0) { printf("Controller::SetTo: no audio or video tracks found\n"); - delete mf; _NotifyFileChanged(); return B_MEDIA_NO_HANDLER; } - printf("Controller::SetTo: %d audio track, %d video track\n", AudioTrackCount(), VideoTrackCount()); + printf("Controller::SetTo: %d audio track, %d video track\n", + AudioTrackCount(), VideoTrackCount()); + mediaFileDeleter.Detach(); fMediaFile = mf; SelectAudioTrack(0); SelectVideoTrack(0); + fVideoView->DisableOverlay(); + + int width; + int height; + GetSize(&width, &height); + + Init(BRect(0, 0, width - 1, height - 1), fVideoFrameRate, + LOOPING_ALL, false); + + SetCurrentFrame(0); + + if (fAutoplay) + StartPlaying(true); + _NotifyFileChanged(); - _NotifyPlaybackStateChanged(); return B_OK; } @@ -293,16 +268,16 @@ Controller::SetTo(const entry_ref &ref) void Controller::GetSize(int *width, int *height) { - BAutolock _(fVideoSupplierLock); + BAutolock _(this); - if (fVideoSupplier) { - media_format format = fVideoSupplier->Format(); + if (fVideoTrackSupplier) { + media_format format = fVideoTrackSupplier->Format(); // TODO: take aspect ratio into account! *height = format.u.raw_video.display.line_count; *width = format.u.raw_video.display.line_width; } else { - *height = 480; - *width = 640; + *height = 0; + *width = 0; } } @@ -310,36 +285,44 @@ Controller::GetSize(int *width, int *height) int Controller::AudioTrackCount() { - BAutolock _(fDataLock); + BAutolock _(this); - return fAudioTrackList->CountItems(); + return fAudioTrackList.CountItems(); } int Controller::VideoTrackCount() { - BAutolock _(fDataLock); + BAutolock _(this); - return fVideoTrackList->CountItems(); + return fVideoTrackList.CountItems(); } status_t Controller::SelectAudioTrack(int n) { - BAutolock _(fAudioSupplierLock); + BAutolock _(this); - BMediaTrack* track = (BMediaTrack *)fAudioTrackList->ItemAt(n); + BMediaTrack* track = (BMediaTrack *)fAudioTrackList.ItemAt(n); if (!track) return B_ERROR; - delete fAudioSupplier; - fAudioSupplier = new MediaTrackAudioSupplier(track); + ObjectDeleter deleter(fAudioTrackSupplier); + fAudioTrackSupplier = new MediaTrackAudioSupplier(track); - bigtime_t a = fAudioSupplier->Duration(); - bigtime_t v = fVideoSupplier ? fVideoSupplier->Duration() : 0;; + bigtime_t a = fAudioTrackSupplier->Duration(); + bigtime_t v = fVideoTrackSupplier ? fVideoTrackSupplier->Duration() : 0;; fDuration = max_c(a, v); + DurationChanged(); + // TODO: notify duration changed! + + // TODO: Not good, because the ProxyAudioSupplier currently + // uses the supplier without locking the Controller! + // This is only a problem when selecting a different audio track + // from the interface menu. + fAudioSupplier->SetSupplier(fAudioTrackSupplier, fVideoFrameRate); _NotifyAudioTrackChanged(n); return B_OK; @@ -349,19 +332,24 @@ Controller::SelectAudioTrack(int n) status_t Controller::SelectVideoTrack(int n) { - BAutolock _(fVideoSupplierLock); + BAutolock _(this); - BMediaTrack* track = (BMediaTrack *)fVideoTrackList->ItemAt(n); + BMediaTrack* track = (BMediaTrack *)fVideoTrackList.ItemAt(n); if (!track) return B_ERROR; - delete fVideoSupplier; - fVideoSupplier = new MediaTrackVideoSupplier(track, + ObjectDeleter deleter(fVideoTrackSupplier); + fVideoTrackSupplier = new MediaTrackVideoSupplier(track, IsOverlayActive() ? B_YCbCr422 : B_RGB32); - bigtime_t a = fAudioSupplier ? fAudioSupplier->Duration() : 0; - bigtime_t v = fVideoSupplier->Duration(); + bigtime_t a = fAudioTrackSupplier ? fAudioTrackSupplier->Duration() : 0; + bigtime_t v = fVideoTrackSupplier->Duration(); fDuration = max_c(a, v); + fVideoFrameRate = fVideoTrackSupplier->Format().u.raw_video.field_rate; + DurationChanged(); + // TODO: notify duration changed! + + fVideoSupplier->SetSupplier(fVideoTrackSupplier); _NotifyVideoTrackChanged(n); return B_OK; @@ -376,20 +364,10 @@ Controller::Stop() { printf("Controller::Stop\n"); - BAutolock _(fDataLock); + BAutolock _(this); - if (fStopped) - return; - - fPaused = false; - fStopped = true; - - fSeekAudio = true; - fSeekVideo = true; - fSeekPosition = 0; - - _NotifyPlaybackStateChanged(); - _UpdatePosition(0, false, true); + StopPlaying(); + SetCurrentFrame(0); } @@ -398,21 +376,15 @@ Controller::Play() { printf("Controller::Play\n"); - BAutolock _(fDataLock); + BAutolock _(this); - if (!fPaused && !fStopped) - return; - - fStopped = false; - fPaused = false; - if (fSeekToStartAfterPause) { printf("seeking to start after pause\n"); SetPosition(0); fSeekToStartAfterPause = false; } - _NotifyPlaybackStateChanged(); + StartPlaying(); } @@ -421,73 +393,45 @@ Controller::Pause() { printf("Controller::Pause\n"); - BAutolock _(fDataLock); + BAutolock _(this); - if (fStopped || fPaused) - return; - - fPaused = true; - - _NotifyPlaybackStateChanged(); + PausePlaying(); } void Controller::TogglePlaying() { - BAutolock _(fDataLock); + printf("Controller::TogglePlaying\n"); - if (IsPaused() || IsStopped()) - Play(); - else - Pause(); -} + BAutolock _(this); - -bool -Controller::IsPaused() const -{ - BAutolock _(fDataLock); - - return fPaused; -} - - -bool -Controller::IsStopped() const -{ - BAutolock _(fDataLock); - - return fStopped; + NodeManager::TogglePlaying(); } uint32 -Controller::PlaybackState() const +Controller::PlaybackState() { - BAutolock _(fDataLock); + BAutolock _(this); - if (fStopped) - return PLAYBACK_STATE_STOPPED; - if (fPaused) - return PLAYBACK_STATE_PAUSED; - return PLAYBACK_STATE_PLAYING; + return _PlaybackState(PlaybackManager::PlayMode()); } bigtime_t -Controller::Duration() +Controller::TimeDuration() { - BAutolock _(fDataLock); + BAutolock _(this); return fDuration; } bigtime_t -Controller::Position() +Controller::TimePosition() { - BAutolock _(fDataLock); + BAutolock _(this); return fPosition; } @@ -507,8 +451,9 @@ Controller::SetVolume(float value) ToggleMute(); fVolume = value; - if (fSoundOutput) - fSoundOutput->SetVolume(fVolume); +// TODO: apply to AutioProducer node +// if (fSoundOutput) +// fSoundOutput->SetVolume(fVolume); _NotifyVolumeChanged(fVolume); } @@ -537,13 +482,14 @@ Controller::ToggleMute() return; fMuted = !fMuted; - - if (fSoundOutput) { - if (fMuted) - fSoundOutput->SetVolume(0.0); - else - fSoundOutput->SetVolume(fVolume); - } + +// TODO: apply to AudioProducer node +// if (fSoundOutput) { +// if (fMuted) +// fSoundOutput->SetVolume(0.0); +// else +// fSoundOutput->SetVolume(fVolume); +// } _NotifyMutedChanged(fMuted); @@ -552,9 +498,9 @@ Controller::ToggleMute() float -Controller::Volume() const +Controller::Volume() { - BAutolock _(fDataLock); + BAutolock _(this); return fVolume; } @@ -563,18 +509,11 @@ Controller::Volume() const void Controller::SetPosition(float value) { - printf("Controller::SetPosition %.4f\n", value); + BAutolock _(this); - BAutolock _(fDataLock); - - fSeekPosition = bigtime_t(value * Duration()); - fSeekAudio = true; - fSeekVideo = true; + SetCurrentFrame(Duration() * value); fSeekToStartAfterPause = false; - -// release_sem(fAudioWaitSem); -// release_sem(fVideoWaitSem); } @@ -641,8 +580,8 @@ status_t Controller::GetEncodedVideoFormat(media_format* format) { // you need to hold the data lock - if (fVideoSupplier) - return fVideoSupplier->GetEncodedFormat(format); + if (fVideoTrackSupplier) + return fVideoTrackSupplier->GetEncodedFormat(format); return B_NO_INIT; } @@ -651,8 +590,8 @@ status_t Controller::GetVideoCodecInfo(media_codec_info* info) { // you need to hold the data lock - if (fVideoSupplier) - return fVideoSupplier->GetCodecInfo(info); + if (fVideoTrackSupplier) + return fVideoTrackSupplier->GetCodecInfo(info); return B_NO_INIT; } @@ -661,8 +600,8 @@ status_t Controller::GetEncodedAudioFormat(media_format* format) { // you need to hold the data lock - if (fAudioSupplier) - return fAudioSupplier->GetEncodedFormat(format); + if (fAudioTrackSupplier) + return fAudioTrackSupplier->GetEncodedFormat(format); return B_NO_INIT; } @@ -671,8 +610,8 @@ status_t Controller::GetAudioCodecInfo(media_codec_info* info) { // you need to hold the data lock - if (fAudioSupplier) - return fAudioSupplier->GetCodecInfo(info); + if (fAudioTrackSupplier) + return fAudioTrackSupplier->GetCodecInfo(info); return B_NO_INIT; } @@ -683,49 +622,29 @@ Controller::GetAudioCodecInfo(media_codec_info* info) void Controller::SetVideoView(VideoView *view) { - BAutolock _(fDataLock); + BAutolock _(this); fVideoView = view; - fVideoView->SetController(this); } bool Controller::IsOverlayActive() { -// return true; + if (fVideoView) + return fVideoView->IsOverlayActive(); + return false; } -bool -Controller::LockBitmap() -{ - return fBitmapLock.Lock(); -} - - -void -Controller::UnlockBitmap() -{ - fBitmapLock.Unlock(); -} - - -BBitmap * -Controller::Bitmap() -{ - return fCurrentBitmap; -} - - // #pragma mark - bool Controller::AddListener(Listener* listener) { - BAutolock _(fDataLock); + BAutolock _(this); if (listener && !fListeners.HasItem(listener)) return fListeners.AddItem(listener); @@ -736,473 +655,44 @@ Controller::AddListener(Listener* listener) void Controller::RemoveListener(Listener* listener) { - BAutolock _(fDataLock); + BAutolock _(this); fListeners.RemoveItem(listener); } -// #pragma mark - +// #pragma mark - Private -void -Controller::_AudioDecodeThread() +uint32 +Controller::_PlaybackState(int32 playingMode) const { - AudioSupplier* lastSupplier = NULL; - size_t bufferSize = 0; - size_t frameSize = 0; - bool decodeError = false; - -printf("audio decode start\n"); - - while (acquire_sem(fAudioDecodeSem) == B_OK) { -//printf("audio decoding..\n"); - buffer_info *buffer = &fAudioBuffer[fAudioBufferWriteIndex]; - fAudioBufferWriteIndex = (fAudioBufferWriteIndex + 1) % fAudioBufferCount; - - if (!fAudioSupplierLock.Lock()) - return; - - buffer->formatChanged = lastSupplier != fAudioSupplier; - lastSupplier = fAudioSupplier; - - if (!lastSupplier) { - // no audio supplier - buffer->sizeUsed = 0; - buffer->startTime = 0; - - fAudioSupplierLock.Unlock(); - snooze(10000); - release_sem(fAudioPlaySem); - continue; - } - - if (buffer->formatChanged) { -printf("audio format changed\n"); - buffer->mediaFormat = lastSupplier->Format(); - bufferSize = buffer->mediaFormat.u.raw_audio.buffer_size; - frameSize = (buffer->mediaFormat.u.raw_audio.format & 0xf) - * buffer->mediaFormat.u.raw_audio.channel_count; - } - - if (buffer->sizeMax < bufferSize) { - delete[] buffer->buffer; - buffer->buffer = new char [bufferSize]; - buffer->sizeMax = bufferSize; - } - - if (fSeekAudio) { - bigtime_t pos = fSeekPosition; - lastSupplier->SeekToTime(&pos); - fSeekAudio = false; - decodeError = false; - } - - int64 frames; - if (!decodeError) { - buffer->endOfStream = false; - status_t ret = lastSupplier->ReadFrames(buffer->buffer, - &frames, &buffer->startTime); - decodeError = B_OK != ret; -if (ret != B_OK) -printf("audio decode error: %s\n", strerror(ret)); - if (ret == B_LAST_BUFFER_ERROR) { - _EndOfStreamReached(); - buffer->endOfStream = true; - } - } - if (!decodeError) { - buffer->sizeUsed = frames * frameSize; - } else { - buffer->sizeUsed = 0; - buffer->startTime = 0; - - fAudioSupplierLock.Unlock(); - snooze(10000); - release_sem(fAudioPlaySem); - continue; - } - - fAudioSupplierLock.Unlock(); - - release_sem(fAudioPlaySem); - } -} - - -void -Controller::_AudioPlayThread() -{ - bigtime_t bufferDuration = 0; - bigtime_t audioLatency = 0; - status_t status; - -printf("audio play start\n"); - - while (acquire_sem(fAudioPlaySem) == B_OK) { -//printf("audio playing..\n"); - buffer_info *buffer = &fAudioBuffer[fAudioBufferReadIndex]; - fAudioBufferReadIndex = (fAudioBufferReadIndex + 1) % fAudioBufferCount; - wait: - if (fPaused || fStopped) { -//printf("waiting..\n"); - status = acquire_sem_etc(fVideoWaitSem, 1, B_RELATIVE_TIMEOUT, 50000); - if (status != B_OK && status != B_TIMED_OUT) - break; - goto wait; - } - - if (!fDataLock.Lock()) - return; - - if (buffer->sizeUsed == 0) { - bool pause = fPauseAtEndOfStream && buffer->endOfStream; - fDataLock.Unlock(); - release_sem(fAudioDecodeSem); - if (pause) { - fPauseAtEndOfStream = false; - fSeekToStartAfterPause = true; - Pause(); - } else - snooze(25000); - continue; - } - - if (!fSoundOutput || buffer->formatChanged) { - if (!fSoundOutput - || !(fSoundOutput->Format() == buffer->mediaFormat.u.raw_audio)) { - delete fSoundOutput; - fSoundOutput = new (nothrow) SoundOutput(fRef.name, - buffer->mediaFormat.u.raw_audio); - fSoundOutput->SetVolume(fVolume); -bufferDuration = bigtime_t(1000000.0 - * (buffer->mediaFormat.u.raw_audio.buffer_size - / (buffer->mediaFormat.u.raw_audio.channel_count - * (buffer->mediaFormat.u.raw_audio.format & 0xf))) - / buffer->mediaFormat.u.raw_audio.frame_rate); -printf("audio format changed, new buffer duration %Ld\n", bufferDuration); - } - } - - // TODO: fix performance time update (in case no audio) - if (fTimeSourceLock.Lock()) { - audioLatency = fSoundOutput ? fSoundOutput->Latency() : 0; - fTimeSourceSysTime = system_time() + audioLatency - bufferDuration; - fTimeSourcePerfTime = buffer->startTime; - fTimeSourceLock.Unlock(); - } -// printf("timesource: sys: %Ld perf: %Ld\n", fTimeSourceSysTime, -// fTimeSourcePerfTime); - - if (fSoundOutput) { - if (fSoundOutput->InitCheck() >= B_OK) - fSoundOutput->Play(buffer->buffer, buffer->sizeUsed); - _UpdatePosition(buffer->startTime); - } - - - fDataLock.Unlock(); - - release_sem(fAudioDecodeSem); - } -} - - -// #pragma mark - - - -void -Controller::_VideoDecodeThread() -{ - VideoSupplier* lastSupplier = NULL; - size_t bufferSize = 0; - size_t bytePerRow = 0; - int lineWidth = 0; - int lineCount = 0; - bool decodeError = false; - status_t status; - -printf("video decode start\n"); - - while (acquire_sem(fVideoDecodeSem) == B_OK) { - buffer_info *buffer = &fVideoBuffer[fVideoBufferWriteIndex]; - fVideoBufferWriteIndex = (fVideoBufferWriteIndex + 1) % fVideoBufferCount; - - if (!fVideoSupplierLock.Lock()) - return; - - buffer->formatChanged = lastSupplier != fVideoSupplier; - lastSupplier = fVideoSupplier; - - if (!lastSupplier) { - // no video supplier - buffer->sizeUsed = 0; - buffer->startTime = 0; - - fVideoSupplierLock.Unlock(); - snooze(10000); - release_sem(fVideoDecodeSem); - continue; - } - - - if (buffer->formatChanged) { -//printf("Video track changed\n"); - buffer->mediaFormat = lastSupplier->Format(); - - bytePerRow = buffer->mediaFormat.u.raw_video.display.bytes_per_row; - lineWidth = buffer->mediaFormat.u.raw_video.display.line_width; - lineCount = buffer->mediaFormat.u.raw_video.display.line_count; - bufferSize = lineCount * bytePerRow; - } - - if (buffer->sizeMax != bufferSize) { - LockBitmap(); - - BRect r(0, 0, lineWidth - 1, lineCount - 1); - if (buffer->bitmap == fCurrentBitmap) - fCurrentBitmap = NULL; - delete buffer->bitmap; -printf("allocating bitmap #%ld %d %d %ld\n", - fVideoBufferWriteIndex, lineWidth, lineCount, bytePerRow); - if (buffer->mediaFormat.u.raw_video.display.format == B_YCbCr422) { - buffer->bitmap = new BBitmap(r, B_BITMAP_WILL_OVERLAY - | (fVideoBufferWriteIndex == 0) ? - B_BITMAP_RESERVE_OVERLAY_CHANNEL : 0, - B_YCbCr422, bytePerRow); - } else { - buffer->bitmap = new BBitmap(r, 0, B_RGB32, bytePerRow); - } - status = buffer->bitmap->InitCheck(); - if (status != B_OK) { - printf("bitmap init status %08lx %s\n", status, strerror(status)); - return; - } - buffer->buffer = (char *)buffer->bitmap->Bits(); - buffer->sizeMax = bufferSize; - - UnlockBitmap(); - } - - if (fSeekVideo) { - bigtime_t pos = fSeekPosition; - lastSupplier->SeekToTime(&pos); - fSeekVideo = false; - decodeError = false; - } - - if (!decodeError) { -// printf("reading video frame...\n"); - status_t ret = lastSupplier->ReadFrame(buffer->buffer, - &buffer->startTime); - decodeError = B_OK != ret; - if (ret == B_LAST_BUFFER_ERROR) { - buffer->endOfStream = true; - _EndOfStreamReached(true); - } - } - if (!decodeError) { - buffer->sizeUsed = buffer->sizeMax; - } else { - buffer->sizeUsed = 0; - buffer->startTime = 0; - - fVideoSupplierLock.Unlock(); - snooze(10000); - release_sem(fVideoPlaySem); - continue; - } - - fVideoSupplierLock.Unlock(); - - release_sem(fVideoPlaySem); - } -} - - -void -Controller::_VideoPlayThread() -{ - status_t status; -printf("video decode start\n"); - - while (acquire_sem(fVideoPlaySem) == B_OK) { - - buffer_info *buffer = &fVideoBuffer[fVideoBufferReadIndex]; - fVideoBufferReadIndex = (fVideoBufferReadIndex + 1) % fVideoBufferCount; - wait: - if (fPaused || fStopped) { -//printf("waiting..\n"); - status = acquire_sem_etc(fVideoWaitSem, 1, B_RELATIVE_TIMEOUT, 50000); - if (status != B_OK && status != B_TIMED_OUT) - break; - goto wait; - } - - bigtime_t waituntil; - bigtime_t waitdelta; - bigtime_t now; - if (fTimeSourceLock.Lock()) { - now = system_time(); - waituntil = fTimeSourceSysTime - fTimeSourcePerfTime + buffer->startTime - 1000; - waitdelta = waituntil - now; - fTimeSourceLock.Unlock(); - } else { - // puhh... - return; - } - -#if 0 - char test[100]; - sprintf(test, "sys %.6f perf %.6f, vid %.6f, now %.6f, waituntil %.6f, waitdelta %.6f", - fTimeSourceSysTime / 1E6, fTimeSourcePerfTime / 1E6, - buffer->startTime / 1E6, now / 1E6, - waituntil / 1E6, waitdelta / 1E6); - if (fVideoView->LockLooperWithTimeout(5000) == B_OK) { - fVideoView->Window()->SetTitle(test); - fVideoView->UnlockLooper(); - } -#endif - - if (fAudioSupplierLock.Lock()) { - if (!fAudioSupplier) { - fTimeSourceSysTime = system_time(); - fTimeSourcePerfTime = buffer->startTime; - } - fAudioSupplierLock.Unlock(); - } else { - // puhh... - } - - -#if 1 - if (waitdelta < -150000) { - printf("video: frame %.6f too late, dropped\n", waitdelta / -1E6); - release_sem(fVideoDecodeSem); - continue; - } -#endif - - status = acquire_sem_etc(fVideoWaitSem, 1, B_ABSOLUTE_TIMEOUT, waituntil); - if (status == B_OK) { // interrupted by seeking - printf("video: video wait interruped\n"); - continue; - } - if (status != B_TIMED_OUT) + uint32 state = 0; + switch (playingMode) { + case MODE_PLAYING_PAUSED_FORWARD: + case MODE_PLAYING_PAUSED_BACKWARD: + state = PLAYBACK_STATE_PAUSED; + break; + case MODE_PLAYING_FORWARD: + case MODE_PLAYING_BACKWARD: + state = PLAYBACK_STATE_PLAYING; break; - if (!fDataLock.Lock()) - return; - - if (fVideoView) { - if (buffer->sizeUsed == buffer->sizeMax) { - LockBitmap(); - fCurrentBitmap = buffer->bitmap; - fVideoView->DrawFrame(); - UnlockBitmap(); - - _UpdatePosition(buffer->startTime, true); - } else { - bool pause = fPauseAtEndOfStream && buffer->endOfStream; - fDataLock.Unlock(); - - release_sem(fVideoDecodeSem); - - if (pause) { - fPauseAtEndOfStream = false; - fSeekToStartAfterPause = true; - Pause(); - } -//else -// snooze(25000); - continue; - } - } else - debugger("fVideoView == NULL"); - - fDataLock.Unlock(); - - // snooze(60000); - release_sem(fVideoDecodeSem); + default: + state = PLAYBACK_STATE_STOPPED; + break; } - -// status_t status = acquire_sem_etc(fVideoWaitSem, 1, B_ABSOLUTE_TIMEOUT, buffer->startTime); -// if (status != B_TIMED_OUT) -// return; + return state; } -// #pragma mark - - - -void -Controller::_StartThreads() -{ - if (fAudioDecodeSem > 0) - return; - - fAudioBufferReadIndex = 0; - fAudioBufferWriteIndex = 0; - fVideoBufferReadIndex = 0; - fVideoBufferWriteIndex = 0; - fAudioDecodeSem = create_sem(fAudioBufferCount, "audio decode sem"); - fVideoDecodeSem = create_sem(fVideoBufferCount - 2, "video decode sem"); - fAudioPlaySem = create_sem(0, "audio play sem"); - fVideoPlaySem = create_sem(0, "video play sem"); - fAudioWaitSem = create_sem(0, "audio wait sem"); - fVideoWaitSem = create_sem(0, "video wait sem"); - fAudioDecodeThread = spawn_thread(_AudioDecodeThreadEntry, "audio decode", AUDIO_DECODE_PRIORITY, this); - fVideoDecodeThread = spawn_thread(_VideoDecodeThreadEntry, "video decode", VIDEO_DECODE_PRIORITY, this); - fAudioPlayThread = spawn_thread(_AudioPlayThreadEntry, "audio play", AUDIO_PLAY_PRIORITY, this); - fVideoPlayThread = spawn_thread(_VideoPlayThreadEntry, "video play", VIDEO_PLAY_PRIORITY, this); - resume_thread(fAudioDecodeThread); - resume_thread(fVideoDecodeThread); - resume_thread(fAudioPlayThread); - resume_thread(fVideoPlayThread); -} - - -void -Controller::_StopThreads() -{ - if (fAudioDecodeSem < 0) - return; - - delete_sem(fAudioDecodeSem); - delete_sem(fVideoDecodeSem); - delete_sem(fAudioPlaySem); - delete_sem(fVideoPlaySem); - delete_sem(fAudioWaitSem); - delete_sem(fVideoWaitSem); - - status_t dummy; - wait_for_thread(fAudioDecodeThread, &dummy); - wait_for_thread(fVideoDecodeThread, &dummy); - wait_for_thread(fAudioPlayThread, &dummy); - wait_for_thread(fVideoPlayThread, &dummy); - fAudioDecodeThread = -1; - fVideoDecodeThread = -1; - fAudioPlayThread = -1; - fVideoPlayThread = -1; - fAudioWaitSem = -1; - fVideoWaitSem = -1; - fAudioDecodeSem = -1; - fVideoDecodeSem = -1; - fAudioPlaySem = -1; - fVideoPlaySem = -1; -} - - -// #pragma mark - - - void Controller::_EndOfStreamReached(bool isVideo) { // you need to hold the fDataLock // prefer end of audio stream over end of video stream - if (isVideo && fAudioSupplier) + if (isVideo && fAudioTrackSupplier) return; // NOTE: executed in audio/video decode thread @@ -1213,68 +703,11 @@ Controller::_EndOfStreamReached(bool isVideo) } -void -Controller::_UpdatePosition(bigtime_t position, bool isVideoPosition, bool force) -{ - // you need to hold the fDataLock - - if (!force && fStopped) - return; - - // prefer audio position over video position - if (isVideoPosition && (fAudioSupplier && fSoundOutput - && fSoundOutput->InitCheck() >= B_OK)) - return; - -// BAutolock _(fTimeSourceLock); -// fTimeSourcePerfTime = position; - - fPosition = position; - float scale = fDuration > 0 ? (float)fPosition / fDuration : 0.0; - _NotifyPositionChanged(scale); -} - - -// #pragma mark - - - -int32 -Controller::_AudioDecodeThreadEntry(void *self) -{ - static_cast(self)->_AudioDecodeThread(); - return 0; -} - - -int32 -Controller::_VideoDecodeThreadEntry(void *self) -{ - static_cast(self)->_VideoDecodeThread(); - return 0; -} - - -int32 -Controller::_AudioPlayThreadEntry(void *self) -{ - static_cast(self)->_AudioPlayThread(); - return 0; -} - - -int32 -Controller::_VideoPlayThreadEntry(void *self) -{ - static_cast(self)->_VideoPlayThread(); - return 0; -} - - -// #pragma mark - +// #pragma mark - Notifications void -Controller::_NotifyFileChanged() +Controller::_NotifyFileChanged() const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1286,7 +719,7 @@ Controller::_NotifyFileChanged() void -Controller::_NotifyFileFinished() +Controller::_NotifyFileFinished() const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1298,7 +731,7 @@ Controller::_NotifyFileFinished() void -Controller::_NotifyVideoTrackChanged(int32 index) +Controller::_NotifyVideoTrackChanged(int32 index) const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1310,7 +743,7 @@ Controller::_NotifyVideoTrackChanged(int32 index) void -Controller::_NotifyAudioTrackChanged(int32 index) +Controller::_NotifyAudioTrackChanged(int32 index) const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1322,7 +755,7 @@ Controller::_NotifyAudioTrackChanged(int32 index) void -Controller::_NotifyVideoStatsChanged() +Controller::_NotifyVideoStatsChanged() const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1334,7 +767,7 @@ Controller::_NotifyVideoStatsChanged() void -Controller::_NotifyAudioStatsChanged() +Controller::_NotifyAudioStatsChanged() const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1346,9 +779,8 @@ Controller::_NotifyAudioStatsChanged() void -Controller::_NotifyPlaybackStateChanged() +Controller::_NotifyPlaybackStateChanged(uint32 state) const { - uint32 state = PlaybackState(); BList listeners(fListeners); int32 count = listeners.CountItems(); for (int32 i = 0; i < count; i++) { @@ -1359,7 +791,7 @@ Controller::_NotifyPlaybackStateChanged() void -Controller::_NotifyPositionChanged(float position) +Controller::_NotifyPositionChanged(float position) const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1371,7 +803,7 @@ Controller::_NotifyPositionChanged(float position) void -Controller::_NotifyVolumeChanged(float volume) +Controller::_NotifyVolumeChanged(float volume) const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1383,7 +815,7 @@ Controller::_NotifyVolumeChanged(float volume) void -Controller::_NotifyMutedChanged(bool muted) +Controller::_NotifyMutedChanged(bool muted) const { BList listeners(fListeners); int32 count = listeners.CountItems(); @@ -1392,3 +824,69 @@ Controller::_NotifyMutedChanged(bool muted) listener->MutedChanged(muted); } } + + +void +Controller::NotifyPlayModeChanged(int32 mode) const +{ + _NotifyPlaybackStateChanged(_PlaybackState(mode)); +} + + +void +Controller::NotifyLoopModeChanged(int32 mode) const +{ +} + + +void +Controller::NotifyLoopingEnabledChanged(bool enabled) const +{ +} + + +void +Controller::NotifyVideoBoundsChanged(BRect bounds) const +{ +} + + +void +Controller::NotifyFPSChanged(float fps) const +{ +} + + +void +Controller::NotifyCurrentFrameChanged(int32 frame) const +{ + float position = 0.0; + double duration = (double)fDuration * fVideoFrameRate / 1000000.0; + if (duration > 0) + position = (float)frame / duration; + fPosition = (bigtime_t)(position * fDuration + 0.5); + _NotifyPositionChanged(position); +} + + +void +Controller::NotifySpeedChanged(float speed) const +{ +} + + +void +Controller::NotifyFrameDropped() const +{ +// printf("Controller::NotifyFrameDropped()\n"); +} + + +void +Controller::NotifyStopFrameReached() const +{ + // Currently, this means we reached the end of the current + // file and should play the next file + _NotifyFileFinished(); +} + diff --git a/src/apps/mediaplayer/Controller.h b/src/apps/mediaplayer/Controller.h index 7567447cb4..13a021c68f 100644 --- a/src/apps/mediaplayer/Controller.h +++ b/src/apps/mediaplayer/Controller.h @@ -2,7 +2,7 @@ * Controller.h - Media Player for the Haiku Operating System * * Copyright (C) 2006 Marcus Overhagen - * Copyright (C) 2007 Stephan Aßmus + * Copyright (C) 2007-2008 Stephan Aßmus (MIT Ok) * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License @@ -29,207 +29,156 @@ #include #include -class AudioSupplier; +#include "NodeManager.h" + +class AudioTrackSupplier; class BBitmap; class BMediaFile; class BMediaTrack; +class ProxyAudioSupplier; +class ProxyVideoSupplier; class SoundOutput; -class VideoSupplier; +class VideoTrackSupplier; class VideoView; -class Controller { +class Controller : public NodeManager { public: class Listener { public: - Listener(); - virtual ~Listener(); + Listener(); + virtual ~Listener(); - virtual void FileFinished(); - virtual void FileChanged(); + virtual void FileFinished(); + virtual void FileChanged(); - virtual void VideoTrackChanged(int32 index); - virtual void AudioTrackChanged(int32 index); + virtual void VideoTrackChanged(int32 index); + virtual void AudioTrackChanged(int32 index); - virtual void VideoStatsChanged(); - virtual void AudioStatsChanged(); + virtual void VideoStatsChanged(); + virtual void AudioStatsChanged(); - virtual void PlaybackStateChanged(uint32 state); - virtual void PositionChanged(float position); - virtual void VolumeChanged(float volume); - virtual void MutedChanged(bool muted); + virtual void PlaybackStateChanged(uint32 state); + virtual void PositionChanged(float position); + virtual void VolumeChanged(float volume); + virtual void MutedChanged(bool muted); }; - Controller(); - virtual ~Controller(); + Controller(); + virtual ~Controller(); - bool Lock(); - status_t LockWithTimeout(bigtime_t timeout); - void Unlock(); + // PlaybackManager interface + virtual int64 Duration(); - status_t SetTo(const entry_ref &ref); - void GetSize(int *width, int *height); + // NodeManager interface + virtual VideoTarget* CreateVideoTarget(); + virtual VideoSupplier* CreateVideoSupplier(); + virtual AudioSupplier* CreateAudioSupplier(); - int AudioTrackCount(); - int VideoTrackCount(); + // Controller + status_t SetTo(const entry_ref &ref); + void GetSize(int *width, int *height); + + int AudioTrackCount(); + int VideoTrackCount(); - status_t SelectAudioTrack(int n); - status_t SelectVideoTrack(int n); + status_t SelectAudioTrack(int n); + status_t SelectVideoTrack(int n); - void Stop(); - void Play(); - void Pause(); - void TogglePlaying(); + void Stop(); + void Play(); + void Pause(); + void TogglePlaying(); - bool IsPaused() const; - bool IsStopped() const; - uint32 PlaybackState() const; + uint32 PlaybackState(); - bigtime_t Duration(); - bigtime_t Position(); + bigtime_t TimeDuration(); + bigtime_t TimePosition(); - void SetVolume(float value); - float Volume() const; - void VolumeUp(); - void VolumeDown(); - void ToggleMute(); - void SetPosition(float value); + virtual void SetVolume(float percent); + float Volume(); + void VolumeUp(); + void VolumeDown(); + void ToggleMute(); + void SetPosition(float value); + + bool HasFile(); + status_t GetFileFormatInfo( + media_file_format* fileFormat); + status_t GetCopyright(BString* copyright); + status_t GetLocation(BString* location); + status_t GetName(BString* name); + status_t GetEncodedVideoFormat(media_format* format); + status_t GetVideoCodecInfo(media_codec_info* info); + status_t GetEncodedAudioFormat(media_format* format); + status_t GetAudioCodecInfo(media_codec_info* info); - bool HasFile(); - status_t GetFileFormatInfo(media_file_format* fileFormat); - status_t GetCopyright(BString* copyright); - status_t GetLocation(BString* location); - status_t GetName(BString* name); - status_t GetEncodedVideoFormat(media_format* format); - status_t GetVideoCodecInfo(media_codec_info* info); - status_t GetEncodedAudioFormat(media_format* format); - status_t GetAudioCodecInfo(media_codec_info* info); - // video view - void SetVideoView(VideoView *view); + void SetVideoView(VideoView *view); - bool IsOverlayActive(); + bool IsOverlayActive(); - bool LockBitmap(); - void UnlockBitmap(); - BBitmap * Bitmap(); - // notification support - bool AddListener(Listener* listener); - void RemoveListener(Listener* listener); + bool AddListener(Listener* listener); + void RemoveListener(Listener* listener); private: - void _AudioDecodeThread(); - void _AudioPlayThread(); + uint32 _PlaybackState(int32 playingMode) const; + void _EndOfStreamReached(bool isVideo = false); - void _VideoDecodeThread(); - void _VideoPlayThread(); + void _NotifyFileChanged() const; + void _NotifyFileFinished() const; + void _NotifyVideoTrackChanged(int32 index) const; + void _NotifyAudioTrackChanged(int32 index) const; - void _StartThreads(); - void _StopThreads(); + void _NotifyVideoStatsChanged() const; + void _NotifyAudioStatsChanged() const; - void _EndOfStreamReached(bool isVideo = false); - void _UpdatePosition(bigtime_t position, - bool isVideoPosition = false, - bool force = false); + void _NotifyPlaybackStateChanged(uint32 state) const; + void _NotifyPositionChanged(float position) const; + void _NotifyVolumeChanged(float volume) const; + void _NotifyMutedChanged(bool muted) const; - static int32 _VideoDecodeThreadEntry(void *self); - static int32 _VideoPlayThreadEntry(void *self); - static int32 _AudioDecodeThreadEntry(void *self); - static int32 _AudioPlayThreadEntry(void *self); + // overridden from PlaybackManager so that we + // can use our own Listener mechanism + virtual void NotifyPlayModeChanged(int32 mode) const; + virtual void NotifyLoopModeChanged(int32 mode) const; + virtual void NotifyLoopingEnabledChanged( + bool enabled) const; + virtual void NotifyVideoBoundsChanged(BRect bounds) const; + virtual void NotifyFPSChanged(float fps) const; + virtual void NotifyCurrentFrameChanged(int32 frame) const; + virtual void NotifySpeedChanged(float speed) const; + virtual void NotifyFrameDropped() const; + virtual void NotifyStopFrameReached() const; -private: - void _NotifyFileChanged(); - void _NotifyFileFinished(); - void _NotifyVideoTrackChanged(int32 index); - void _NotifyAudioTrackChanged(int32 index); - void _NotifyVideoStatsChanged(); - void _NotifyAudioStatsChanged(); + VideoView* fVideoView; + volatile bool fPaused; + volatile bool fStopped; + float fVolume; + bool fMuted; - void _NotifyPlaybackStateChanged(); - void _NotifyPositionChanged(float position); - void _NotifyVolumeChanged(float volume); - void _NotifyMutedChanged(bool muted); + entry_ref fRef; + BMediaFile* fMediaFile; - friend class InfoWin; + ProxyVideoSupplier* fVideoSupplier; + ProxyAudioSupplier* fAudioSupplier; + VideoTrackSupplier* fVideoTrackSupplier; + AudioTrackSupplier* fAudioTrackSupplier; - enum { - MAX_AUDIO_BUFFERS = 8, - MAX_VIDEO_BUFFERS = 3, - }; + BList fAudioTrackList; + BList fVideoTrackList; + + mutable bigtime_t fPosition; + bigtime_t fDuration; + float fVideoFrameRate; + + bool fAutoplay; + volatile bool fPauseAtEndOfStream; + volatile bool fSeekToStartAfterPause; - struct buffer_info { - char * buffer; - BBitmap * bitmap; - size_t sizeUsed; - size_t sizeMax; - bigtime_t startTime; - bool formatChanged; - bool endOfStream; - media_format mediaFormat; - }; - - VideoView * fVideoView; - volatile bool fPaused; - volatile bool fStopped; - float fVolume; - bool fMuted; - - entry_ref fRef; - BMediaFile * fMediaFile; - mutable BLocker fDataLock; - - VideoSupplier* fVideoSupplier; - AudioSupplier* fAudioSupplier; - - BLocker fVideoSupplierLock; - BLocker fAudioSupplierLock; - - BList * fAudioTrackList; - BList * fVideoTrackList; - media_format fAudioFormat; - media_format fVideoFormat; - - sem_id fAudioDecodeSem; - sem_id fVideoDecodeSem; - sem_id fAudioPlaySem; - sem_id fVideoPlaySem; - sem_id fAudioWaitSem; - sem_id fVideoWaitSem; - thread_id fAudioDecodeThread; - thread_id fVideoDecodeThread; - thread_id fAudioPlayThread; - thread_id fVideoPlayThread; - - SoundOutput * fSoundOutput; - volatile bool fSeekAudio; - volatile bool fSeekVideo; - volatile bigtime_t fSeekPosition; - bigtime_t fPosition; - bigtime_t fDuration; - - int32 fAudioBufferCount; - int32 fAudioBufferReadIndex; - int32 fAudioBufferWriteIndex; - int32 fVideoBufferCount; - int32 fVideoBufferReadIndex; - int32 fVideoBufferWriteIndex; - - buffer_info fAudioBuffer[MAX_AUDIO_BUFFERS]; - buffer_info fVideoBuffer[MAX_VIDEO_BUFFERS]; - - BLocker fTimeSourceLock; - bigtime_t fTimeSourceSysTime; - bigtime_t fTimeSourcePerfTime; - bool fAutoplay; - volatile bool fPauseAtEndOfStream; - volatile bool fSeekToStartAfterPause; - - BBitmap * fCurrentBitmap; - BLocker fBitmapLock; - - BList fListeners; + BList fListeners; }; diff --git a/src/apps/mediaplayer/InfoWin.cpp b/src/apps/mediaplayer/InfoWin.cpp index 0efb3f9788..dabc3ef614 100644 --- a/src/apps/mediaplayer/InfoWin.cpp +++ b/src/apps/mediaplayer/InfoWin.cpp @@ -227,6 +227,9 @@ printf("InfoWin::Update(0x%08lx)\n", which); fContentsView->SetFontAndColor(be_plain_font, B_FONT_ALL); // fContentsView->Insert(""); + if (!fController->Lock()) + return; + fLabelsView->SetFontAndColor(be_plain_font, B_FONT_ALL, &kRed); status_t err; diff --git a/src/apps/mediaplayer/Jamfile b/src/apps/mediaplayer/Jamfile index bbe7d7a343..645c445c26 100644 --- a/src/apps/mediaplayer/Jamfile +++ b/src/apps/mediaplayer/Jamfile @@ -2,13 +2,16 @@ SubDir HAIKU_TOP src apps mediaplayer ; SetSubDirSupportedPlatformsBeOSCompatible ; -AddSubDirSupportedPlatforms libbe_test ; - # for BRecentItems UsePublicHeaders [ FDirName be_apps Tracker ] ; +UsePrivateHeaders shared ; # source directories local sourceDirs = + interface + media_node_framework + media_node_framework/audio + media_node_framework/video playlist supplier support @@ -21,6 +24,33 @@ for sourceDir in $(sourceDirs) { } Application MediaPlayer : + # interface + DrawingTidbits.cpp + SeekSlider.cpp + TransportButton.cpp + VolumeSlider.cpp + + # media_node_framework + NodeManager.cpp + PlaybackListener.cpp + PlaybackLOAdapter.cpp + PlaybackManager.cpp + + # media_node_framework/audio + AudioAdapter.cpp + AudioChannelConverter.cpp + AudioFormatConverter.cpp + AudioProducer.cpp + AudioReader.cpp + AudioResampler.cpp + AudioSupplier.cpp + + # media_node_framework/video + VideoConsumer.cpp + VideoProducer.cpp + VideoSupplier.cpp + VideoTarget.cpp + # playlist CopyPLItemsCommand.cpp ImportPLItemsCommand.cpp @@ -32,22 +62,27 @@ Application MediaPlayer : PlaylistWindow.cpp RemovePLItemsCommand.cpp - # supplier + # settings Settings.cpp SettingsWindow.cpp # supplier - AudioSupplier.cpp + AudioTrackSupplier.cpp MediaTrackAudioSupplier.cpp MediaTrackVideoSupplier.cpp - VideoSupplier.cpp + ProxyAudioSupplier.cpp + ProxyVideoSupplier.cpp + VideoTrackSupplier.cpp # support AbstractLOAdapter.cpp Command.cpp CommandStack.cpp + Event.cpp + EventQueue.cpp Listener.cpp ListenerAdapter.cpp + MessageEvent.cpp Notifier.cpp RWLocker.cpp FileReadWrite.cpp @@ -57,23 +92,13 @@ Application MediaPlayer : Controller.cpp ControllerObserver.cpp ControllerView.cpp - DrawingTidbits.cpp InfoWin.cpp MainApp.cpp MainWin.cpp - SoundOutput.cpp - TransportButton.cpp TransportControlGroup.cpp - SeekSlider.cpp - VideoNode.cpp VideoView.cpp - VolumeSlider.cpp : be media tracker translation textencoding $(TARGET_LIBSTDC++) : MediaPlayer.rdef ; -if $(TARGET_PLATFORM) = libbe_test { - HaikuInstall install-test-apps : $(HAIKU_APP_TEST_DIR) : MediaPlayer - : tests!apps ; -} diff --git a/src/apps/mediaplayer/MainApp.cpp b/src/apps/mediaplayer/MainApp.cpp index 3b59d2bbd9..9be57d73d4 100644 --- a/src/apps/mediaplayer/MainApp.cpp +++ b/src/apps/mediaplayer/MainApp.cpp @@ -30,6 +30,8 @@ #include #include +#include "EventQueue.h" + MainApp *gMainApp; const char* kAppSig = "application/x-vnd.Haiku-MediaPlayer"; @@ -221,8 +223,13 @@ MainApp::_BroadcastMessage(const BMessage& _message) int main() { + EventQueue::CreateDefault(); + gMainApp = new MainApp; gMainApp->Run(); delete gMainApp; + + EventQueue::DeleteDefault(); + return 0; } diff --git a/src/apps/mediaplayer/MainWin.cpp b/src/apps/mediaplayer/MainWin.cpp index fc852b62b0..f1ccab9c67 100644 --- a/src/apps/mediaplayer/MainWin.cpp +++ b/src/apps/mediaplayer/MainWin.cpp @@ -150,8 +150,7 @@ MainWin::MainWin() // video view rect = BRect(0, fMenuBarHeight, fBackground->Bounds().right, fMenuBarHeight + 10); - fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE, - B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE); + fVideoView = new VideoView(rect, "video display", B_FOLLOW_NONE); fBackground->AddChild(fVideoView); // controls @@ -171,7 +170,6 @@ MainWin::MainWin() fPlaylist->AddListener(fPlaylistObserver); fController->SetVideoView(fVideoView); fController->AddListener(fControllerObserver); - fVideoView->IsOverlaySupported(); // printf("fMenuBarHeight %d\n", fMenuBarHeight); // printf("fControlsHeight %d\n", fControlsHeight); @@ -224,8 +222,13 @@ MainWin::~MainWin() } delete fPlaylist; - delete fController; delete fFilePanel; + + // quit the Controller looper thread + thread_id controllerThread = fController->Thread(); + fController->PostMessage(B_QUIT_REQUESTED); + status_t exitValue; + wait_for_thread(controllerThread, &exitValue); } @@ -1237,10 +1240,7 @@ MainWin::_KeyDown(BMessage *msg) switch (raw_char) { case B_SPACE: - if (fController->IsPaused() || fController->IsStopped()) - fController->Play(); - else - fController->Pause(); + fController->TogglePlaying(); return B_OK; case B_ESCAPE: diff --git a/src/apps/mediaplayer/SoundOutput.cpp b/src/apps/mediaplayer/SoundOutput.cpp deleted file mode 100644 index 02dcc99785..0000000000 --- a/src/apps/mediaplayer/SoundOutput.cpp +++ /dev/null @@ -1,139 +0,0 @@ -/* - * SoundOutput.cpp - Media Player for the Haiku Operating System - * - * Copyright (C) 2006 Marcus Overhagen - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ - -#include "SoundOutput.h" - -#include -#include -#include - -#include -#include - -using std::nothrow; - - -SoundOutput::SoundOutput(const char *name, const media_multi_audio_format &format) - : fSoundPlayer(new(nothrow) BSoundPlayer(static_cast(&format), name, play_buffer, NULL, this)) - , fBuffer(new(nothrow) uint8[format.buffer_size]) - , fBufferSize(format.buffer_size) - , fBufferWriteable(create_sem(1, "SoundOutput writeable")) - , fBufferReadable(create_sem(0, "SoundOutput readable")) - , fBufferDuration(0) - , fIsPlaying(false) -{ - if (format.channel_count > 0 && format.frame_rate > 0 && (format.format & 0xf) != 0) - fBufferDuration = bigtime_t(1000000.0 * (format.buffer_size / (format.channel_count * (format.format & 0xf))) / format.frame_rate); - printf("SoundOutput: buffer duration is %Ld\n", fBufferDuration); -} - - -SoundOutput::~SoundOutput() -{ - delete fSoundPlayer; - delete_sem(fBufferWriteable); - delete_sem(fBufferReadable); - delete [] fBuffer; -} - - -status_t -SoundOutput::InitCheck() -{ - if (!fSoundPlayer || !fBuffer || fBufferSize <= 0 || fBufferWriteable < B_OK || fBufferReadable < B_OK) - return B_ERROR; - return fSoundPlayer->InitCheck(); -} - - -media_raw_audio_format -SoundOutput::Format() const -{ - return fSoundPlayer->Format(); -} - - -bigtime_t -SoundOutput::Latency() -{ - bigtime_t latency = 0; - if (InitCheck() >= B_OK) - latency += fSoundPlayer->Latency(); - // Because of buffering, latency of SoundOutput is - // slightly higher then the BSoundPlayer latency. - return latency + min_c(1000, fBufferDuration / 4); -} - - -float -SoundOutput::Volume() -{ - return fSoundPlayer->Volume(); -} - - -void -SoundOutput::SetVolume(float new_volume) -{ - fSoundPlayer->SetVolume(new_volume); -} - - -void -SoundOutput::Play(const void *data, size_t size) -{ - ASSERT(size > 0 && size <= fBufferSize); - acquire_sem(fBufferWriteable); - memcpy(fBuffer, data, size); - - size_t fillsize = fBufferSize - size; - if (fillsize) - memset(fBuffer + size, 0, fillsize); - release_sem(fBufferReadable); - - if (!fIsPlaying) { - fSoundPlayer->SetHasData(true); - fSoundPlayer->Start(); - fIsPlaying = true; - } -} - - -void -SoundOutput::PlayBuffer(void *buffer) -{ - if (acquire_sem_etc(fBufferReadable, 1, B_RELATIVE_TIMEOUT, fBufferDuration / 2) != B_OK) { -// printf("SoundOutput: buffer not ready, playing silence\n"); - memset(buffer, 0, fBufferSize); - return; - } - memcpy(buffer, fBuffer, fBufferSize); - release_sem(fBufferWriteable); -} - - -void -SoundOutput::play_buffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format & format) -{ - ASSERT(size == static_cast(cookie)->fBufferSize); - ASSERT(size == format.buffer_size); - static_cast(cookie)->PlayBuffer(buffer); -} - diff --git a/src/apps/mediaplayer/SoundOutput.h b/src/apps/mediaplayer/SoundOutput.h deleted file mode 100644 index 260e5bb1ed..0000000000 --- a/src/apps/mediaplayer/SoundOutput.h +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SoundOutput.h - Media Player for the Haiku Operating System - * - * Copyright (C) 2006 Marcus Overhagen - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * - */ -#ifndef __SOUND_OUTPUT_H -#define __SOUND_OUTPUT_H - -#include - -class BSoundPlayer; - -class SoundOutput -{ -public: - SoundOutput(const char *name, const media_multi_audio_format &format); - ~SoundOutput(); - - status_t InitCheck(); - - media_raw_audio_format Format() const; - - bigtime_t Latency(); - - float Volume(); - void SetVolume(float new_volume); - - void Play(const void *data, size_t size); - -private: - static void play_buffer(void *cookie, void *buffer, size_t size, const media_raw_audio_format & format); - void PlayBuffer(void *buffer); - -private: - BSoundPlayer * fSoundPlayer; - uint8 * fBuffer; - size_t fBufferSize; - sem_id fBufferWriteable; - sem_id fBufferReadable; - bigtime_t fBufferDuration; - bool fIsPlaying; -}; - -#endif diff --git a/src/apps/mediaplayer/VideoView.cpp b/src/apps/mediaplayer/VideoView.cpp index ad70a55783..5a524fd156 100644 --- a/src/apps/mediaplayer/VideoView.cpp +++ b/src/apps/mediaplayer/VideoView.cpp @@ -1,269 +1,169 @@ /* - * VideoView.cpp - Media Player for the Haiku Operating System - * - * Copyright (C) 2006 Marcus Overhagen - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT license. */ -#include -#include #include "VideoView.h" #include -#include -VideoView::VideoView(BRect frame, const char *name, uint32 resizeMask, uint32 flags) - : BView(frame, name, resizeMask, flags) - , fController(NULL) - , fOverlayActive(false) +#include + + +VideoView::VideoView(BRect frame, const char* name, uint32 resizeMask) + : BView(frame, name, resizeMask, B_WILL_DRAW | B_FULL_UPDATE_ON_RESIZE), + fOverlayMode(false) { SetViewColor(B_TRANSPARENT_COLOR); -// SetViewColor(127,255,127); - rgb_color r = {255, 0, 0, 255}; - fOverlayKeyColor = r; + // might be reset to overlay key color if overlays are used + SetHighColor(0, 0, 0); + + // create some hopefully sensible default overlay restrictions + fOverlayRestrictions.min_width_scale = 0.25; + fOverlayRestrictions.max_width_scale = 8.0; + fOverlayRestrictions.min_height_scale = 0.25; + fOverlayRestrictions.max_height_scale = 8.0; } VideoView::~VideoView() -{ -} - - -void -VideoView::SetController(Controller *controller) -{ - fController = controller; -} - - -void -VideoView::AttachedToWindow() { -} - -void -VideoView::OverlayLockAcquire() -{ - printf("VideoView::OverlayLockAcquire\n"); -} - - -void -VideoView::OverlayLockRelease() -{ - printf("VideoView::OverlayLockRelease\n"); - // overlaybitmap->UnlockBits -} - - -void -VideoView::OverlayScreenshotPrepare() -{ - printf("OverlayScreenshotPrepare enter\n"); -/* - fController->LockBitmap(); - if (fOverlayActive) { - BBitmap *bmp = fController->Bitmap(); - if (bmp) { -// Window()->UpdateIfNeeded(); -// Sync(); - BBitmap *tmp = new BBitmap(bmp->Bounds(), 0, B_RGB32); -// ConvertBitmap(tmp, bmp); - ClearViewOverlay(); - DrawBitmap(tmp, Bounds()); - delete tmp; -// Sync(); - } - } - fController->UnlockBitmap(); -*/ - printf("OverlayScreenshotPrepare leave\n"); -} - - -void -VideoView::OverlayScreenshotCleanup() -{ - printf("OverlayScreenshotCleanup enter\n"); -/* - snooze(50000); // give app server some time to take the screenshot - fController->LockBitmap(); - if (fOverlayActive) { - BBitmap *bmp = fController->Bitmap(); - if (bmp) { - DrawBitmap(bmp, Bounds()); - SetViewOverlay(bmp, bmp->Bounds(), Bounds(), &fOverlayKeyColor, - B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL); - Invalidate(); - } - } - fController->UnlockBitmap(); -*/ - printf("OverlayScreenshotCleanup leave\n"); -} - - -void -VideoView::RemoveVideoDisplay() -{ - printf("VideoView::RemoveVideoDisplay\n"); - - if (fOverlayActive) { - ClearViewOverlay(); - fOverlayActive = false; - } - Invalidate(); -} - - -void -VideoView::RemoveOverlay() -{ - printf("VideoView::RemoveOverlay\n"); - if (LockLooperWithTimeout(50000) == B_OK) { - ClearViewOverlay(); - fOverlayActive = false; - UnlockLooper(); - } } void VideoView::Draw(BRect updateRect) { - if (fOverlayActive) { - SetHighColor(fOverlayKeyColor); + bool fillBlack = true; + + if (LockBitmap()) { + BRect r(Bounds()); + if (const BBitmap* bitmap = GetBitmap()) { + fillBlack = false; + if (!fOverlayMode) + DrawBitmap(bitmap, bitmap->Bounds(), r); + } + UnlockBitmap(); + } + + if (fillBlack) FillRect(updateRect); - } else { - fController->LockBitmap(); - BBitmap *bmp = fController->Bitmap(); - if (bmp) - DrawBitmap(bmp, Bounds()); - fController->UnlockBitmap(); - } } void -VideoView::DrawFrame() +VideoView::SetBitmap(const BBitmap* bitmap) { -// printf("VideoView::DrawFrame\n"); - - if (LockLooperWithTimeout(50000) != B_OK) - return; + VideoTarget::SetBitmap(bitmap); + // Attention: Don't lock the window, if the bitmap is NULL. Otherwise + // we're going to deadlock when the window tells the node manager to + // stop the nodes (Window -> NodeManager -> VideoConsumer -> VideoView + // -> Window). + if (bitmap && LockLooperWithTimeout(10000) == B_OK) { + if (LockBitmap()) { +// if (fOverlayMode || bitmap->Flags() & B_BITMAP_WILL_OVERLAY) { + if (fOverlayMode || bitmap->ColorSpace() == B_YCbCr422) { + if (!fOverlayMode) { + // init overlay + rgb_color key; + status_t ret = SetViewOverlay(bitmap, bitmap->Bounds(), + Bounds(), &key, B_FOLLOW_ALL, + B_OVERLAY_FILTER_HORIZONTAL + | B_OVERLAY_FILTER_VERTICAL); + if (ret == B_OK) { + fOverlayKeyColor = key; + SetViewColor(key); + SetLowColor(key); + snooze(20000); + FillRect(Bounds(), B_SOLID_LOW); + Sync(); + // use overlay from here on + fOverlayMode = true; - fController->LockBitmap(); - BBitmap *bmp = fController->Bitmap(); - - if (bmp) { - bool want_overlay = bmp->ColorSpace() == B_YCbCr422; - - if (!want_overlay && fOverlayActive) { - if (LockLooperWithTimeout(50000) == B_OK) { + // update restrictions + overlay_restrictions restrictions; + if (bitmap->GetOverlayRestrictions(&restrictions) + == B_OK) + fOverlayRestrictions = restrictions; + } else { + // try again next time + // synchronous draw + FillRect(Bounds()); + Sync(); + } + } else { + // transfer overlay channel + rgb_color key; + SetViewOverlay(bitmap, bitmap->Bounds(), Bounds(), + &key, B_FOLLOW_ALL, B_OVERLAY_FILTER_HORIZONTAL + | B_OVERLAY_FILTER_VERTICAL + | B_OVERLAY_TRANSFER_CHANNEL); + } + } else if (fOverlayMode && bitmap->ColorSpace() != B_YCbCr422) { + fOverlayMode = false; ClearViewOverlay(); - UnlockLooper(); - fOverlayActive = false; - } else { - printf("can't ClearViewOverlay, as LockLooperWithTimeout failed\n"); - return; + SetViewColor(B_TRANSPARENT_COLOR); } - } + if (!fOverlayMode) + DrawBitmap(bitmap, bitmap->Bounds(), Bounds()); - if (want_overlay && !fOverlayActive ) { -printf("trying to activate overlay..."); - // reserve overlay channel - status_t ret = SetViewOverlay(bmp, bmp->Bounds(), Bounds(), - &fOverlayKeyColor, B_FOLLOW_ALL, - B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL); - if (ret == B_OK) { -printf("success\n"); - fOverlayActive = true; - Invalidate(); - } else { -printf("failed: %s\n", strerror(ret)); - } - } else if (fOverlayActive) { - // transfer overlay channel - rgb_color overlayKey; - SetViewOverlay(bmp, bmp->Bounds(), Bounds(), &overlayKey, - B_FOLLOW_ALL, B_OVERLAY_TRANSFER_CHANNEL - | B_OVERLAY_FILTER_HORIZONTAL | B_OVERLAY_FILTER_VERTICAL); - } else { - // no overlay - DrawBitmap(bmp, Bounds()); + UnlockBitmap(); } + UnlockLooper(); } - - fController->UnlockBitmap(); - - UnlockLooper(); } void -VideoView::MessageReceived(BMessage *msg) +VideoView::GetOverlayScaleLimits(float* minScale, float* maxScale) const { - switch (msg->what) { + *minScale = max_c(fOverlayRestrictions.min_width_scale, + fOverlayRestrictions.min_height_scale); + *maxScale = max_c(fOverlayRestrictions.max_width_scale, + fOverlayRestrictions.max_height_scale); +} - default: - BView::MessageReceived(msg); - } + +void +VideoView::OverlayScreenshotPrepare() +{ + // TODO: Do nothing if the current bitmap is in RGB color space + // and no overlay. Otherwise, convert current bitmap to RGB color + // space an draw it in place of the normal display. +} + + +void +VideoView::OverlayScreenshotCleanup() +{ + // TODO: Do nothing if the current bitmap is in RGB color space + // and no overlay. Otherwise clean view area with overlay color. } bool -VideoView::IsOverlaySupported() +VideoView::IsOverlayActive() { - struct colorcombo { - color_space colspace; - const char *name; - } colspace[] = { - { B_RGB32, "B_RGB32"}, - { B_RGBA32, "B_RGBA32"}, - { B_RGB24, "B_RGB24"}, - { B_RGB16, "B_RGB16"}, - { B_RGB15, "B_RGB15"}, - { B_RGBA15, "B_RGBA15"}, - { B_RGB32_BIG, "B_RGB32_BIG"}, - { B_RGBA32_BIG, "B_RGBA32_BIG "}, - { B_RGB24_BIG, "B_RGB24_BIG "}, - { B_RGB16_BIG, "B_RGB16_BIG "}, - { B_RGB15_BIG, "B_RGB15_BIG "}, - { B_RGBA15_BIG, "B_RGBA15_BIG "}, - { B_YCbCr422, "B_YCbCr422"}, - { B_YCbCr411, "B_YCbCr411"}, - { B_YCbCr444, "B_YCbCr444"}, - { B_YCbCr420, "B_YCbCr420"}, - { B_YUV422, "B_YUV422"}, - { B_YUV411, "B_YUV411"}, - { B_YUV444, "B_YUV444"}, - { B_YUV420, "B_YUV420"}, - { B_NO_COLOR_SPACE, NULL} - }; - - bool supported = false; - for (int i = 0; colspace[i].name; i++) { - BBitmap *test = new BBitmap(BRect(0,0,319,239), B_BITMAP_WILL_OVERLAY | B_BITMAP_RESERVE_OVERLAY_CHANNEL, colspace[i].colspace); - if (test->InitCheck() == B_OK) { - printf("Display supports %s (0x%08x) overlay\n", colspace[i].name, colspace[i].colspace); - supported = true; - } - delete test; -// if (supported) -// break; + bool active = false; + if (LockBitmap()) { + active = fOverlayMode; + UnlockBitmap(); } - return supported; + return active; +} + + +void +VideoView::DisableOverlay() +{ + if (!fOverlayMode) + return; + + FillRect(Bounds()); + Sync(); + + ClearViewOverlay(); + snooze(20000); + Sync(); + fOverlayMode = false; } diff --git a/src/apps/mediaplayer/VideoView.h b/src/apps/mediaplayer/VideoView.h index 7ff789369e..e4be3e1910 100644 --- a/src/apps/mediaplayer/VideoView.h +++ b/src/apps/mediaplayer/VideoView.h @@ -1,58 +1,42 @@ /* - * VideoView.h - Media Player for the Haiku Operating System - * - * Copyright (C) 2006 Marcus Overhagen - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * version 2 as published by the Free Software Foundation. - * - * 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 General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - * + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT license. */ -#ifndef __VIDEO_VIEW_H -#define __VIDEO_VIEW_H +#ifndef VIDEO_VIEW_H +#define VIDEO_VIEW_H + #include -#include "Controller.h" -class VideoView : public BView -{ +#include "VideoTarget.h" + + +class VideoView : public BView, public VideoTarget { public: - VideoView(BRect frame, const char *name, uint32 resizeMask, uint32 flags); - ~VideoView(); + VideoView(BRect frame, const char* name, + uint32 resizeMask); + virtual ~VideoView(); - void SetController(Controller *controller); - - void RemoveVideoDisplay(); - void RemoveOverlay(); - - bool IsOverlaySupported(); + // BView interface + virtual void Draw(BRect updateRect); - void OverlayLockAcquire(); - void OverlayLockRelease(); + // VideoTarget interface + virtual void SetBitmap(const BBitmap* bitmap); - void OverlayScreenshotPrepare(); - void OverlayScreenshotCleanup(); - - void DrawFrame(); + // VideoView + void GetOverlayScaleLimits(float* minScale, + float* maxScale) const; + + void OverlayScreenshotPrepare(); + void OverlayScreenshotCleanup(); + + bool IsOverlayActive(); + void DisableOverlay(); private: - void AttachedToWindow(); - void MessageReceived(BMessage *msg); - void Draw(BRect updateRect); - -private: - Controller * fController; - volatile bool fOverlayActive; - rgb_color fOverlayKeyColor; + bool fOverlayMode; + overlay_restrictions fOverlayRestrictions; + rgb_color fOverlayKeyColor; }; -#endif +#endif // VIDEO_VIEW_H diff --git a/src/apps/mediaplayer/ButtonBitmaps.h b/src/apps/mediaplayer/interface/ButtonBitmaps.h similarity index 100% rename from src/apps/mediaplayer/ButtonBitmaps.h rename to src/apps/mediaplayer/interface/ButtonBitmaps.h diff --git a/src/apps/mediaplayer/DrawingTidbits.cpp b/src/apps/mediaplayer/interface/DrawingTidbits.cpp similarity index 100% rename from src/apps/mediaplayer/DrawingTidbits.cpp rename to src/apps/mediaplayer/interface/DrawingTidbits.cpp diff --git a/src/apps/mediaplayer/DrawingTidbits.h b/src/apps/mediaplayer/interface/DrawingTidbits.h similarity index 100% rename from src/apps/mediaplayer/DrawingTidbits.h rename to src/apps/mediaplayer/interface/DrawingTidbits.h diff --git a/src/apps/mediaplayer/SeekSlider.cpp b/src/apps/mediaplayer/interface/SeekSlider.cpp similarity index 100% rename from src/apps/mediaplayer/SeekSlider.cpp rename to src/apps/mediaplayer/interface/SeekSlider.cpp diff --git a/src/apps/mediaplayer/SeekSlider.h b/src/apps/mediaplayer/interface/SeekSlider.h similarity index 100% rename from src/apps/mediaplayer/SeekSlider.h rename to src/apps/mediaplayer/interface/SeekSlider.h diff --git a/src/apps/mediaplayer/TransportButton.cpp b/src/apps/mediaplayer/interface/TransportButton.cpp similarity index 100% rename from src/apps/mediaplayer/TransportButton.cpp rename to src/apps/mediaplayer/interface/TransportButton.cpp diff --git a/src/apps/mediaplayer/TransportButton.h b/src/apps/mediaplayer/interface/TransportButton.h similarity index 100% rename from src/apps/mediaplayer/TransportButton.h rename to src/apps/mediaplayer/interface/TransportButton.h diff --git a/src/apps/mediaplayer/VolumeSlider.cpp b/src/apps/mediaplayer/interface/VolumeSlider.cpp similarity index 100% rename from src/apps/mediaplayer/VolumeSlider.cpp rename to src/apps/mediaplayer/interface/VolumeSlider.cpp diff --git a/src/apps/mediaplayer/VolumeSlider.h b/src/apps/mediaplayer/interface/VolumeSlider.h similarity index 100% rename from src/apps/mediaplayer/VolumeSlider.h rename to src/apps/mediaplayer/interface/VolumeSlider.h diff --git a/src/apps/mediaplayer/media_node_framework/NodeManager.cpp b/src/apps/mediaplayer/media_node_framework/NodeManager.cpp new file mode 100644 index 0000000000..9bb39ca662 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/NodeManager.cpp @@ -0,0 +1,688 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "NodeManager.h" + +#include +#include + +#include +#include +#include + +#include "AudioProducer.h" +#include "AudioSupplier.h" +#include "VideoConsumer.h" +#include "VideoProducer.h" +#include "VideoSupplier.h" + +// debugging +//#define TRACE_NODE_MANAGER +#ifdef TRACE_NODE_MANAGER +# define TRACE(x...) printf(x) +# define ERROR(x...) fprintf(stderr, x) +#else +# define TRACE(x...) +# define ERROR(x...) fprintf(stderr, x) +#endif + +#define print_error(str, status) printf(str ", error: %s\n", strerror(status)) + +NodeManager::Connection::Connection() + : connected(false) +{ + memset(&format, 0, sizeof(media_format)); +} + +// constructor +NodeManager::NodeManager() + : PlaybackManager(), + fMediaRoster(NULL), + fAudioProducer(NULL), + fVideoConsumer(NULL), + fVideoProducer(NULL), + fTimeSource(media_node::null), + fAudioConnection(), + fVideoConnection(), + fPerformanceTimeBase(0), + fStatus(B_NO_INIT), + fVideoTarget(NULL), + fAudioSupplier(NULL), + fVideoSupplier(NULL), + fVideoBounds(0, 0, -1, -1) +{ +} + +// destructor +NodeManager::~NodeManager() +{ + _StopNodes(); + _TearDownNodes(); +} + +// Init +status_t +NodeManager::Init(BRect videoBounds, float videoFrameRate, int32 loopingMode, + bool loopingEnabled, float speed) +{ + // init base class + PlaybackManager::Init(videoFrameRate, loopingMode, loopingEnabled, speed); + + // get some objects from a derived class + if (!fVideoTarget) + fVideoTarget = CreateVideoTarget(); + + if (!fVideoSupplier) + fVideoSupplier = CreateVideoSupplier(); + + if (!fAudioSupplier) + fAudioSupplier = CreateAudioSupplier(); + + return FormatChanged(videoBounds, videoFrameRate, true); +} + +// InitCheck +status_t +NodeManager::InitCheck() +{ + return fStatus; +} + +// SetPlayMode +void +NodeManager::SetPlayMode(int32 mode, bool continuePlaying) +{ +// if (fMediaRoster && fMediaRoster->Lock()) { +// BMediaNode::run_mode runMode = mode > 0 ? +// BMediaNode::B_DROP_DATA : BMediaNode::B_OFFLINE; +// fMediaRoster->SetRunModeNode(fVideoConnection.consumer, runMode); +// fMediaRoster->Unlock(); +// } + + PlaybackManager::SetPlayMode(mode, continuePlaying); +} + +// CleanupNodes +status_t +NodeManager::CleanupNodes() +{ + _StopNodes(); + return _TearDownNodes(false); +} + +// FormatChanged +status_t +NodeManager::FormatChanged(BRect videoBounds, float videoFrameRate, bool force) +{ + if (!force && videoBounds == VideoBounds() + && videoFrameRate == FramesPerSecond()) + return B_OK; + + if (videoFrameRate != FramesPerSecond()) { + PlaybackManager::Init(videoFrameRate, LoopMode(), IsLoopingEnabled(), + Speed(), MODE_PLAYING_PAUSED_FORWARD, CurrentFrame()); + } + + _StopNodes(); + _TearDownNodes(); + + SetVideoBounds(videoBounds); + + status_t ret = _SetUpNodes(); + if (ret == B_OK) + _StartNodes(); + else + fprintf(stderr, "unable to setup nodes: %s\n", strerror(ret)); + + return ret; +} + +// RealTimeForTime +bigtime_t +NodeManager::RealTimeForTime(bigtime_t time) const +{ + bigtime_t result = 0; + if (fVideoProducer) { + result = fVideoProducer->TimeSource()->RealTimeFor( + fPerformanceTimeBase + time, 0); + } + return result; +} + +// TimeForRealTime +bigtime_t +NodeManager::TimeForRealTime(bigtime_t time) const +{ + bigtime_t result = 0; + if (fVideoProducer) { + result = fVideoProducer->TimeSource()->PerformanceTimeFor(time) + - fPerformanceTimeBase; + } else if (fAudioProducer) { + result = fAudioProducer->TimeSource()->PerformanceTimeFor(time) + - fPerformanceTimeBase; + } + return result; +} + +// SetCurrentAudioTime +void +NodeManager::SetCurrentAudioTime(bigtime_t time) +{ +//printf("NodeManager::SetCurrentAudioTime(%lld)\n", time); + PlaybackManager::SetCurrentAudioTime(time); + if (!fVideoProducer) { + // running without video, update video time as well + PlaybackManager::SetCurrentVideoTime(time); + } +} + +// SetVideoBounds +void +NodeManager::SetVideoBounds(BRect bounds) +{ + if (bounds != fVideoBounds) { + fVideoBounds = bounds; + NotifyVideoBoundsChanged(fVideoBounds); + } +} + +// VideoBounds +BRect +NodeManager::VideoBounds() const +{ + return fVideoBounds; +} + +// SetVideoTarget +void +NodeManager::SetVideoTarget(VideoTarget* videoTarget) +{ + if (videoTarget != fVideoTarget) { + fVideoTarget = videoTarget; + if (fVideoConsumer) + fVideoConsumer->SetTarget(fVideoTarget); + } +} + +// GetVideoTarget +VideoTarget* +NodeManager::GetVideoTarget() const +{ + return fVideoTarget; +} + +// SetVolume +void +NodeManager::SetVolume(float percent) +{ + // TODO: would be nice to set the volume on the system mixer input of + // our audio node... +} + +// #pragma mark - + +// _SetUpNodes +status_t +NodeManager::_SetUpNodes() +{ +printf("NodeManager::_SetUpNodes()\n"); + + // find the media roster + fStatus = B_OK; + fMediaRoster = BMediaRoster::Roster(&fStatus); + if (fStatus != B_OK) { + print_error("Can't find the media roster", fStatus); + fMediaRoster = NULL; + return fStatus; + } + if (!fMediaRoster->Lock()) + return B_ERROR; + + // find the time source + fStatus = fMediaRoster->GetTimeSource(&fTimeSource); + if (fStatus != B_OK) { + print_error("Can't get a time source", fStatus); + fMediaRoster->Unlock(); + return fStatus; + } + + // setup the video nodes + if (fVideoBounds.IsValid()) { + fStatus = _SetUpVideoNodes(); + if (fStatus != B_OK) { + print_error("Error setting up video nodes", fStatus); + fMediaRoster->Unlock(); + return fStatus; + } + } else + printf("running without video node\n"); + + // setup the audio nodes + fStatus = _SetUpAudioNodes(); + if (fStatus != B_OK) { + print_error("Error setting up video nodes", fStatus); + fMediaRoster->Unlock(); + return fStatus; + } + + + // we're done mocking with the media roster + fMediaRoster->Unlock(); + + return fStatus; +} + + +// _SetUpVideoNodes +status_t +NodeManager::_SetUpVideoNodes() +{ + // create the video producer node + fVideoProducer = new VideoProducer(NULL, "MediaPlayer Video Out", 0, + this, fVideoSupplier); + + // register the producer node + fStatus = fMediaRoster->RegisterNode(fVideoProducer); + if (fStatus != B_OK) { + print_error("Can't register the video producer", fStatus); + return fStatus; + } + + // make sure the Media Roster knows that we're using the node +// fMediaRoster->GetNodeFor(fVideoProducer->Node().node, +// &fVideoConnection.producer); + fVideoConnection.producer = fVideoProducer->Node(); + + // create the video consumer node + fVideoConsumer = new VideoConsumer("MediaPlayer Video In", NULL, 0, this, + fVideoTarget); + + // register the consumer node + fStatus = fMediaRoster->RegisterNode(fVideoConsumer); + if (fStatus != B_OK) { + print_error("Can't register the video consumer", fStatus); + return fStatus; + } + + // make sure the Media Roster knows that we're using the node +// fMediaRoster->GetNodeFor(fVideoConsumer->Node().node, +// &fVideoConnection.consumer); + fVideoConnection.consumer = fVideoConsumer->Node(); + + // find free producer output + media_input videoInput; + media_output videoOutput; + int32 count = 1; + fStatus = fMediaRoster->GetFreeOutputsFor(fVideoConnection.producer, + &videoOutput, 1, &count, B_MEDIA_RAW_VIDEO); + if (fStatus != B_OK || count < 1) { + fStatus = B_RESOURCE_UNAVAILABLE; + print_error("Can't find an available video stream", fStatus); + return fStatus; + } + + // find free consumer input + count = 1; + fStatus = fMediaRoster->GetFreeInputsFor(fVideoConnection.consumer, + &videoInput, 1, &count, B_MEDIA_RAW_VIDEO); + if (fStatus != B_OK || count < 1) { + fStatus = B_RESOURCE_UNAVAILABLE; + print_error("Can't find an available connection to the video window", + fStatus); + return fStatus; + } + + // connect the nodes + media_format format; + format.type = B_MEDIA_RAW_VIDEO; + media_raw_video_format videoFormat = { + FramesPerSecond(), 1, 0, + fVideoBounds.IntegerWidth(), + B_VIDEO_TOP_LEFT_RIGHT, 1, 1, + { + B_YCbCr422, + fVideoBounds.IntegerWidth() + 1, + fVideoBounds.IntegerHeight() + 1, + 0, 0, 0 + } + }; + format.u.raw_video = videoFormat; + + // connect video producer to consumer (B_YCbCr422) + fStatus = fMediaRoster->Connect(videoOutput.source, videoInput.destination, + &format, &videoOutput, &videoInput); + + if (fStatus != B_OK) { + print_error("Can't connect the video source to the video window... " + "trying B_RGB32", fStatus); + format.u.raw_video.display.format = B_RGB32; + // connect video producer to consumer (B_RGB32) + fStatus = fMediaRoster->Connect(videoOutput.source, + videoInput.destination, &format, &videoOutput, &videoInput); + } + // bail if second attempt failed too + if (fStatus != B_OK) { + print_error("Can't connect the video source to the video window", + fStatus); + return fStatus; + } + + // the inputs and outputs might have been reassigned during the + // nodes' negotiation of the Connect(). That's why we wait until + // after Connect() finishes to save their contents. + fVideoConnection.format = format; + fVideoConnection.source = videoOutput.source; + fVideoConnection.destination = videoInput.destination; + fVideoConnection.connected = true; + + // set time sources + fStatus = fMediaRoster->SetTimeSourceFor(fVideoConnection.producer.node, + fTimeSource.node); + if (fStatus != B_OK) { + print_error("Can't set the timesource for the video source", fStatus); + return fStatus; + } + + fStatus = fMediaRoster->SetTimeSourceFor(fVideoConsumer->ID(), + fTimeSource.node); + if (fStatus != B_OK) { + print_error("Can't set the timesource for the video window", fStatus); + return fStatus; + } + + return fStatus; +} + +// _SetUpAudioNodes +status_t +NodeManager::_SetUpAudioNodes() +{ + fAudioProducer = new AudioProducer("MediaPlayer Audio Out", fAudioSupplier); + fStatus = fMediaRoster->RegisterNode(fAudioProducer); + if (fStatus != B_OK) { + print_error("unable to register audio producer node!\n", fStatus); + return fStatus; + } + // make sure the Media Roster knows that we're using the node +// fMediaRoster->GetNodeFor(fAudioProducer->Node().node, +// &fAudioConnection.producer); + fAudioConnection.producer = fAudioProducer->Node(); + + // connect to the mixer + fStatus = fMediaRoster->GetAudioMixer(&fAudioConnection.consumer); + if (fStatus != B_OK) { + print_error("unable to get the system mixer", fStatus); + return fStatus; + } + + fMediaRoster->SetTimeSourceFor(fAudioConnection.producer.node, + fTimeSource.node); + + // got the nodes; now we find the endpoints of the connection + media_input mixerInput; + media_output soundOutput; + int32 count = 1; + fStatus = fMediaRoster->GetFreeOutputsFor(fAudioConnection.producer, + &soundOutput, 1, &count); + if (fStatus != B_OK) { + print_error("unable to get a free output from the producer node", + fStatus); + return fStatus; + } + count = 1; + fStatus = fMediaRoster->GetFreeInputsFor(fAudioConnection.consumer, + &mixerInput, 1, &count); + if (fStatus != B_OK) { + print_error("unable to get a free input to the mixer", fStatus); + return fStatus; + } + + // got the endpoints; now we connect it! + media_format audio_format; + audio_format.type = B_MEDIA_RAW_AUDIO; + audio_format.u.raw_audio = media_raw_audio_format::wildcard; + fStatus = fMediaRoster->Connect(soundOutput.source, mixerInput.destination, + &audio_format, &soundOutput, &mixerInput); + if (fStatus != B_OK) { + print_error("unable to connect audio nodes", fStatus); + return fStatus; + } + + // the inputs and outputs might have been reassigned during the + // nodes' negotiation of the Connect(). That's why we wait until + // after Connect() finishes to save their contents. + fAudioConnection.format = audio_format; + fAudioConnection.source = soundOutput.source; + fAudioConnection.destination = mixerInput.destination; + fAudioConnection.connected = true; + + // Set an appropriate run mode for the producer + fMediaRoster->SetRunModeNode(fAudioConnection.producer, + BMediaNode::B_INCREASE_LATENCY); + + return fStatus; +} + +// _TearDownNodes +status_t +NodeManager::_TearDownNodes(bool disconnect) +{ +TRACE("NodeManager::_TearDownNodes()\n"); + status_t err = B_OK; + fMediaRoster = BMediaRoster::Roster(&err); + if (err != B_OK) { + fprintf(stderr, "NodeManager::_TearDownNodes() - error getting media " + "roster: %s\n", strerror(err)); + fMediaRoster = NULL; + } + // begin mucking with the media roster + bool mediaRosterLocked = false; + if (fMediaRoster && fMediaRoster->Lock()) + mediaRosterLocked = true; + + if (fVideoConsumer && fVideoProducer && fVideoConnection.connected) { + // disconnect + if (fMediaRoster) { +TRACE(" disconnecting video...\n"); + err = fMediaRoster->Disconnect(fVideoConnection.producer.node, + fVideoConnection.source, fVideoConnection.consumer.node, + fVideoConnection.destination); + if (err < B_OK) + print_error("unable to disconnect video nodes", err); + } else { + fprintf(stderr, "NodeManager::_TearDownNodes() - cannot " + "disconnect video nodes, no media server!\n"); + } + fVideoConnection.connected = false; + } + if (fVideoProducer) { +TRACE(" releasing video producer...\n"); + fVideoProducer->Release(); + fVideoProducer = NULL; + } + if (fVideoConsumer) { +TRACE(" releasing video consumer...\n"); + fVideoConsumer->Release(); + fVideoConsumer = NULL; + } + if (fAudioProducer) { + disconnect = fAudioConnection.connected; + // Ordinarily we'd stop *all* of the nodes in the chain at this point. + // However, one of the nodes is the System Mixer, and stopping the + // Mixer is a Bad Idea (tm). So, we just disconnect from it, and + // release our references to the nodes that we're using. We *are* + // supposed to do that even for global nodes like the Mixer. + if (fMediaRoster && disconnect) { +TRACE(" disconnecting audio...\n"); + err = fMediaRoster->Disconnect(fAudioConnection.producer.node, + fAudioConnection.source, fAudioConnection.consumer.node, + fAudioConnection.destination); + if (err < B_OK) { + print_error("unable to disconnect audio nodes", err); + disconnect = false; + } + } else { + fprintf(stderr, "NodeManager::_TearDownNodes() - cannot " + "disconnect audio nodes, no media server!\n"); + } + +TRACE(" releasing audio producer...\n"); + fAudioProducer->Release(); + fAudioProducer = NULL; + fAudioConnection.connected = false; + + if (fMediaRoster && disconnect) { +TRACE(" releasing audio consumer...\n"); + fMediaRoster->ReleaseNode(fAudioConnection.consumer); + } else { + fprintf(stderr, "NodeManager::_TearDownNodes() - cannot release " + "audio consumer (system mixer)!\n"); + } + } + // we're done mucking with the media roster + if (mediaRosterLocked && fMediaRoster) + fMediaRoster->Unlock(); +TRACE("NodeManager::_TearDownNodes() done\n"); + return err; +} + +// _StartNodes +status_t +NodeManager::_StartNodes() +{ + status_t status = B_NO_INIT; + if (!fMediaRoster || !fAudioProducer) + return status; + // begin mucking with the media roster + if (fMediaRoster->Lock()) { + bigtime_t latency = 0; + bigtime_t initLatency = 0; + if (fVideoProducer && fVideoConsumer) { + // figure out what recording delay to use + status = fMediaRoster->GetLatencyFor(fVideoConnection.producer, + &latency); + if (status < B_OK) { + print_error("error getting latency for video producer", + status); + } else + TRACE("video latency: %Ld\n", latency); + status = fMediaRoster->SetProducerRunModeDelay( + fVideoConnection.producer, latency); + if (status < B_OK) { + print_error("error settings run mode delay for video producer", + status); + } + + // start the nodes + status = fMediaRoster->GetInitialLatencyFor( + fVideoConnection.producer, &initLatency); + if (status < B_OK) { + print_error("error getting initial latency for video producer", + status); + } + } + initLatency += estimate_max_scheduling_latency(); + + bigtime_t audioLatency = 0; + status = fMediaRoster->GetLatencyFor(fAudioConnection.producer, + &audioLatency); + TRACE("audio latency: %Ld\n", audioLatency); + + BTimeSource* timeSource; + if (fVideoProducer) { + timeSource = fMediaRoster->MakeTimeSourceFor( + fVideoConnection.producer); + } else { + timeSource = fMediaRoster->MakeTimeSourceFor( + fAudioConnection.producer); + } + bool running = timeSource->IsRunning(); + + // workaround for people without sound cards + // because the system time source won't be running + bigtime_t real = BTimeSource::RealTime(); + if (!running) { + status = fMediaRoster->StartTimeSource(fTimeSource, real); + if (status != B_OK) { + timeSource->Release(); + print_error("cannot start time source!", status); + return status; + } + status = fMediaRoster->SeekTimeSource(fTimeSource, 0, real); + if (status != B_OK) { + timeSource->Release(); + print_error("cannot seek time source!", status); + return status; + } + } + + bigtime_t perf = timeSource->PerformanceTimeFor(real + latency + + initLatency); + + timeSource->Release(); + + // start the nodes + if (fVideoProducer && fVideoConsumer) { + status = fMediaRoster->StartNode(fVideoConnection.consumer, perf); + if (status != B_OK) { + print_error("Can't start the video consumer", status); + return status; + } + status = fMediaRoster->StartNode(fVideoConnection.producer, perf); + if (status != B_OK) { + print_error("Can't start the video producer", status); + return status; + } + } + + fAudioProducer->SetRunning(true); + status = fMediaRoster->StartNode(fAudioConnection.producer, perf); + if (status != B_OK) { + print_error("Can't start the audio producer", status); + return status; + } + fPerformanceTimeBase = perf; + // done mucking with the media roster + fMediaRoster->Unlock(); + } + return status; +} + +// _StopNodes +void +NodeManager::_StopNodes() +{ + TRACE("NodeManager::_StopNodes()\n"); +// if (PlayMode() == MODE_PLAYING_PAUSED_FORWARD +// || PlayMode() == MODE_PLAYING_PAUSED_FORWARD) +// return; + fMediaRoster = BMediaRoster::Roster(); + if (!fMediaRoster) { + if (fAudioProducer) + fAudioProducer->SetRunning(false); + return; + } else if (fMediaRoster->Lock()) { + // begin mucking with the media roster + if (fVideoProducer) { + TRACE(" stopping video producer...\n"); + fMediaRoster->StopNode(fVideoConnection.producer, 0, true); + } + if (fAudioProducer) { + TRACE(" stopping audio producer...\n"); + fAudioProducer->SetRunning(false); + fMediaRoster->StopNode(fAudioConnection.producer, 0, true); + // synchronous stop + } + if (fVideoConsumer) { + TRACE(" stopping video consumer...\n"); + fMediaRoster->StopNode(fVideoConnection.consumer, 0, true); + } + TRACE(" all nodes stopped\n"); + // done mucking with the media roster + fMediaRoster->Unlock(); + } + TRACE("NodeManager::_StopNodes() done\n"); +} + diff --git a/src/apps/mediaplayer/media_node_framework/NodeManager.h b/src/apps/mediaplayer/media_node_framework/NodeManager.h new file mode 100644 index 0000000000..538e46aba7 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/NodeManager.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ + +//! This class controls our media nodes and general playback + +#ifndef NODE_MANAGER_H +#define NODE_MANAGER_H + +#include + +#include "PlaybackManager.h" + +class AudioProducer; +class VideoTarget; +class VideoProducer; +class VideoConsumer; +class AudioSupplier; +class VideoSupplier; + +class NodeManager : public PlaybackManager { + public: + NodeManager(); + virtual ~NodeManager(); + + // must be implemented in derived classes + virtual VideoTarget* CreateVideoTarget() = 0; + virtual VideoSupplier* CreateVideoSupplier() = 0; + virtual AudioSupplier* CreateAudioSupplier() = 0; + + // NodeManager + status_t Init(BRect videoBounds, float videoFrameRate, + int32 loopingMode = LOOPING_ALL, + bool loopingEnabled = true, + float speed = 1.0); + status_t InitCheck(); + // only call this if the + // media_server has died! + status_t CleanupNodes(); + + status_t FormatChanged(BRect videoBounds, + float videoFrameRate, bool force = false); + virtual void SetPlayMode(int32 mode, + bool continuePlaying = true); + + virtual bigtime_t RealTimeForTime(bigtime_t time) const; + virtual bigtime_t TimeForRealTime(bigtime_t time) const; + + virtual void SetCurrentAudioTime(bigtime_t time); + + void SetVideoBounds(BRect bounds); + virtual BRect VideoBounds() const; + + void SetVideoTarget(VideoTarget* vcTarget); + VideoTarget* GetVideoTarget() const; + + virtual void SetVolume(float percent); + + private: + status_t _SetUpNodes(); + status_t _SetUpVideoNodes(); + status_t _SetUpAudioNodes(); + status_t _TearDownNodes(bool disconnect = true); + status_t _StartNodes(); + void _StopNodes(); + + private: + struct Connection { + Connection(); + + media_node producer; + media_node consumer; + media_source source; + media_destination destination; + media_format format; + bool connected; + }; + +private: + BMediaRoster* fMediaRoster; + + // media nodes + AudioProducer* fAudioProducer; + VideoConsumer* fVideoConsumer; + VideoProducer* fVideoProducer; + media_node fTimeSource; + + Connection fAudioConnection; + Connection fVideoConnection; + + bigtime_t fPerformanceTimeBase; + + status_t fStatus; + // + VideoTarget* fVideoTarget; + AudioSupplier* fAudioSupplier; + VideoSupplier* fVideoSupplier; + BRect fVideoBounds; +}; + + +#endif // NODE_MANAGER_H diff --git a/src/apps/mediaplayer/media_node_framework/PlaybackLOAdapter.cpp b/src/apps/mediaplayer/media_node_framework/PlaybackLOAdapter.cpp new file mode 100644 index 0000000000..67341235ba --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/PlaybackLOAdapter.cpp @@ -0,0 +1,96 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include + +#include "PlaybackLOAdapter.h" + + +PlaybackLOAdapter::PlaybackLOAdapter(BHandler* handler) + : AbstractLOAdapter(handler) +{ +} + + +PlaybackLOAdapter::PlaybackLOAdapter(const BMessenger& messenger) + : AbstractLOAdapter(messenger) +{ +} + + +PlaybackLOAdapter::~PlaybackLOAdapter() +{ +} + + +void +PlaybackLOAdapter::PlayModeChanged(int32 mode) +{ + BMessage message(MSG_PLAYBACK_PLAY_MODE_CHANGED); + message.AddInt32("play mode", mode); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::LoopModeChanged(int32 mode) +{ + BMessage message(MSG_PLAYBACK_LOOP_MODE_CHANGED); + message.AddInt32("loop mode", mode); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::LoopingEnabledChanged(bool enabled) +{ + BMessage message(MSG_PLAYBACK_LOOPING_ENABLED_CHANGED); + message.AddBool("looping enabled", enabled); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::VideoBoundsChanged(BRect bounds) +{ + BMessage message(MSG_PLAYBACK_VIDEO_BOUNDS_CHANGED); + message.AddRect("video bounds", bounds); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::FramesPerSecondChanged(float fps) +{ + BMessage message(MSG_PLAYBACK_FPS_CHANGED); + message.AddFloat("fps", fps); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::CurrentFrameChanged(double frame) +{ + BMessage message(MSG_PLAYBACK_CURRENT_FRAME_CHANGED); + message.AddDouble("current frame", frame); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::SpeedChanged(float speed) +{ + BMessage message(MSG_PLAYBACK_SPEED_CHANGED); + message.AddFloat("speed", speed); + DeliverMessage(message); +} + + +void +PlaybackLOAdapter::FrameDropped() +{ + DeliverMessage(MSG_PLAYBACK_FRAME_DROPPED); +} + diff --git a/src/apps/mediaplayer/media_node_framework/PlaybackLOAdapter.h b/src/apps/mediaplayer/media_node_framework/PlaybackLOAdapter.h new file mode 100644 index 0000000000..2c9e92740c --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/PlaybackLOAdapter.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef PLAYBACK_LO_ADAPTER_H +#define PLAYBACK_LO_ADAPTER_H + + +#include "AbstractLOAdapter.h" +#include "PlaybackListener.h" + + +enum { + MSG_PLAYBACK_PLAY_MODE_CHANGED = 'ppmc', + MSG_PLAYBACK_LOOP_MODE_CHANGED = 'plmc', + MSG_PLAYBACK_LOOPING_ENABLED_CHANGED = 'plec', + MSG_PLAYBACK_VIDEO_BOUNDS_CHANGED = 'pmbc', + MSG_PLAYBACK_FPS_CHANGED = 'pfps', + MSG_PLAYBACK_CURRENT_FRAME_CHANGED = 'pcfc', + MSG_PLAYBACK_SPEED_CHANGED = 'pspc', + MSG_PLAYBACK_FRAME_DROPPED = 'pfdr', +}; + + +class PlaybackLOAdapter : public AbstractLOAdapter, public PlaybackListener { + public: + PlaybackLOAdapter(BHandler* handler); + PlaybackLOAdapter( + const BMessenger& messenger); + virtual ~PlaybackLOAdapter(); + + virtual void PlayModeChanged(int32 mode); + virtual void LoopModeChanged(int32 mode); + virtual void LoopingEnabledChanged(bool enabled); + virtual void VideoBoundsChanged(BRect bounds); + virtual void FramesPerSecondChanged(float fps); + virtual void CurrentFrameChanged(double frame); + virtual void SpeedChanged(float speed); + virtual void FrameDropped(); +}; + +#endif // PLAYBACK_LO_ADAPTER_H diff --git a/src/apps/mediaplayer/media_node_framework/PlaybackListener.cpp b/src/apps/mediaplayer/media_node_framework/PlaybackListener.cpp new file mode 100644 index 0000000000..1e023b6ae1 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/PlaybackListener.cpp @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "PlaybackListener.h" + +#include + + +PlaybackListener::PlaybackListener() +{ +} + + +PlaybackListener::~PlaybackListener() +{ +} + + +void +PlaybackListener::PlayModeChanged(int32 mode) +{ +} + + +void +PlaybackListener::LoopModeChanged(int32 mode) +{ +} + + +void +PlaybackListener::LoopingEnabledChanged(bool enabled) +{ +} + + +void +PlaybackListener::VideoBoundsChanged(BRect bounds) +{ +} + + +void +PlaybackListener::FramesPerSecondChanged(float fps) +{ +} + + +void +PlaybackListener::CurrentFrameChanged(double frame) +{ +} + + +void +PlaybackListener::SpeedChanged(float speed) +{ +} + + +void +PlaybackListener::FrameDropped() +{ +} + + diff --git a/src/apps/mediaplayer/media_node_framework/PlaybackListener.h b/src/apps/mediaplayer/media_node_framework/PlaybackListener.h new file mode 100644 index 0000000000..48cc55b00b --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/PlaybackListener.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ + +/*! This class listens to a PlaybackManager + + The hooks are called by PlaybackManager after it executed a command, + to keep every listener informed. FrameDropped() is something the nodes + can call and it is passed onto the contollers, so that they can respond + by displaying some kind of warning. */ + +#ifndef PLAYBACK_LISTENER_H +#define PLAYBACK_LISTENER_H + +#include +#include + +class PlaybackListener { + public: + PlaybackListener(); + virtual ~PlaybackListener(); + + virtual void PlayModeChanged(int32 mode); + virtual void LoopModeChanged(int32 mode); + virtual void LoopingEnabledChanged(bool enabled); + + virtual void VideoBoundsChanged(BRect bounds); + virtual void FramesPerSecondChanged(float fps); + virtual void SpeedChanged(float speed); + + virtual void CurrentFrameChanged(double frame); + + virtual void FrameDropped(); +}; + + +#endif // PLAYBACK_LISTENER_H diff --git a/src/apps/mediaplayer/media_node_framework/PlaybackManager.cpp b/src/apps/mediaplayer/media_node_framework/PlaybackManager.cpp new file mode 100644 index 0000000000..d425850fa9 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/PlaybackManager.cpp @@ -0,0 +1,1567 @@ +// PlaybackManager.cpp + +#include +#include + +#include +#include +#include + +#include "EventQueue.h" +#include "MessageEvent.h" +#include "PlaybackListener.h" + +#include "PlaybackManager.h" + +//#define TRACE_NODE_MANAGER +#ifdef TRACE_NODE_MANAGER +# define TRACE(x...) printf(x) +# define ERROR(x...) fprintf(stderr, x) +#else +# define TRACE(x...) +# define ERROR(x...) fprintf(stderr, x) +#endif + +void +tdebug(const char* str) +{ + TRACE("[%lx, %lld] ", find_thread(NULL), system_time()); + TRACE(str); +} + + +struct PlaybackManager::PlayingState { + int64 start_frame; + int64 end_frame; + int64 frame_count; + int64 first_visible_frame; + int64 last_visible_frame; + int64 max_frame_count; + int32 play_mode; + int32 loop_mode; + bool looping_enabled; + int64 current_frame; // Playlist frame + int64 range_index; // playing range index of current_frame + int64 activation_frame; // absolute video frame +}; + + +struct PlaybackManager::SpeedInfo { + int64 activation_frame; // absolute video frame + bigtime_t activation_time; // performance time + float speed; // speed to be used for calculations, + // is 1.0 if not playing + float set_speed; // speed set by the user +}; + + +// #pragma mark - PlaybackManager + + +PlaybackManager::PlaybackManager() + : BLooper("playback manager"), + fStates(10), + fSpeeds(10), + fCurrentAudioTime(0), + fCurrentVideoTime(0), + fPerformanceTime(0), + fFrameRate(1.0), + fStopPlayingFrame(-1), + fListeners() +{ + Run(); +} + + +PlaybackManager::~PlaybackManager() +{ + Cleanup(); +} + + +void +PlaybackManager::Init(float frameRate, int32 loopingMode, bool loopingEnabled, + float playbackSpeed, int32 playMode, int32 currentFrame) +{ + // cleanup first + Cleanup(); + + // set the new frame rate + fFrameRate = frameRate; + fCurrentAudioTime = 0; + fCurrentVideoTime = 0; + fPerformanceTime = 0; + fStopPlayingFrame = -1; + + // set up the initial speed + SpeedInfo* speed = new SpeedInfo; + speed->activation_frame = 0; + speed->activation_time = 0; + speed->speed = playbackSpeed; + speed->set_speed = playbackSpeed; + _PushSpeedInfo(speed); + + // set up the initial state + PlayingState* state = new PlayingState; + state->frame_count = Duration(); + state->start_frame = 0; + state->end_frame = 0; + state->first_visible_frame = 0; + state->last_visible_frame = 0; + state->max_frame_count = state->frame_count; + + state->play_mode = MODE_PLAYING_PAUSED_FORWARD; + state->loop_mode = loopingMode; + state->looping_enabled = loopingEnabled; + state->current_frame = currentFrame; + state->activation_frame = 0; + fStates.AddItem(state); + + TRACE("initial state pushed: state count: %ld\n", fStates.CountItems()); + + // notify the listeners + NotifyPlayModeChanged(PlayMode()); + NotifyLoopModeChanged(LoopMode()); + NotifyLoopingEnabledChanged(IsLoopingEnabled()); + NotifyVideoBoundsChanged(VideoBounds()); + NotifyFPSChanged(FramesPerSecond()); + NotifySpeedChanged(Speed()); + NotifyCurrentFrameChanged(CurrentFrame()); + SetPlayMode(playMode); +} + + +void +PlaybackManager::Cleanup() +{ + // delete states + for (int32 i = 0; PlayingState* state = _StateAt(i); i++) + delete state; + fStates.MakeEmpty(); + + // delete speed infos + for (int32 i = 0; SpeedInfo* speed = _SpeedInfoAt(i); i++) + delete speed; + fSpeeds.MakeEmpty(); +} + + +void +PlaybackManager::MessageReceived(BMessage* message) +{ + switch (message->what) { + case MSG_EVENT: + SetPerformanceTime(TimeForRealTime(system_time())); +//TRACE("MSG_EVENT: rt: %lld, pt: %lld\n", system_time(), fPerformanceTime); + break; + + case MSG_PLAYBACK_FORCE_UPDATE: + { + int64 frame; + if (message->FindInt64("frame", &frame) < B_OK) + frame = CurrentFrame(); + SetCurrentFrame(frame); + break; + } + + case MSG_PLAYBACK_SET_RANGE: + { + int64 startFrame = _LastState()->start_frame; + int64 endFrame = _LastState()->end_frame; + message->FindInt64("start frame", &startFrame); + message->FindInt64("end frame", &endFrame); + if (startFrame != _LastState()->start_frame + || endFrame != _LastState()->end_frame) { + PlayingState* state = new PlayingState(*_LastState()); + state->start_frame = startFrame; + state->end_frame = endFrame; + _PushState(state, true); + } + break; + } + + case MSG_PLAYBACK_SET_VISIBLE: + { + int64 startFrame = _LastState()->first_visible_frame; + int64 endFrame = _LastState()->last_visible_frame; + message->FindInt64("start frame", &startFrame); + message->FindInt64("end frame", &endFrame); + if (startFrame != _LastState()->first_visible_frame + || endFrame != _LastState()->last_visible_frame) { + PlayingState* state = new PlayingState(*_LastState()); + state->first_visible_frame = startFrame; + state->last_visible_frame = endFrame; + _PushState(state, true); + } + break; + } + + case MSG_PLAYBACK_SET_LOOP_MODE: + { + int32 loopMode = _LastState()->loop_mode; + message->FindInt32("mode", &loopMode); + if (loopMode != _LastState()->loop_mode) { + PlayingState* state = new PlayingState(*_LastState()); + state->loop_mode = loopMode; + _PushState(state, true); + } + break; + } + + // other messages + default: + BLooper::MessageReceived(message); + break; + } +} + +// #pragma mark - playback control + + +void +PlaybackManager::StartPlaying(bool atBeginning) +{ + int32 playMode = PlayMode(); + if (playMode == MODE_PLAYING_PAUSED_FORWARD) + SetPlayMode(MODE_PLAYING_FORWARD, !atBeginning); + if (playMode == MODE_PLAYING_PAUSED_BACKWARD) + SetPlayMode(MODE_PLAYING_BACKWARD, !atBeginning); +} + + +void +PlaybackManager::StopPlaying() +{ + int32 playMode = PlayMode(); + if (playMode == MODE_PLAYING_FORWARD) + SetPlayMode(MODE_PLAYING_PAUSED_FORWARD, true); + if (playMode == MODE_PLAYING_BACKWARD) + SetPlayMode(MODE_PLAYING_PAUSED_BACKWARD, true); +} + + +void +PlaybackManager::TogglePlaying(bool atBeginning) +{ + // playmodes (paused <-> playing) are the negative of each other + SetPlayMode(-PlayMode(), !atBeginning); +} + + +void +PlaybackManager::PausePlaying() +{ + if (PlayMode() > 0) + TogglePlaying(); +} + + +bool +PlaybackManager::IsPlaying() const +{ + return (PlayMode() > 0); +} + + +int32 +PlaybackManager::PlayMode() const +{ + if (!_LastState()) + return MODE_PLAYING_PAUSED_FORWARD; + return _LastState()->play_mode; +} + + +int32 +PlaybackManager::LoopMode() const +{ + if (!_LastState()) + return LOOPING_ALL; + return _LastState()->loop_mode; +} + + +bool +PlaybackManager::IsLoopingEnabled() const +{ + if (!_LastState()) + return true; + return _LastState()->looping_enabled; +} + + +int32 +PlaybackManager::CurrentFrame() const +{ + return PlaylistFrameAtFrame(FrameForTime(fPerformanceTime)); +} + + +float +PlaybackManager::Speed() const +{ + if (!_LastState()) + return 1.0; + return _LastSpeedInfo()->set_speed; +} + + +void +PlaybackManager::SetFramesPerSecond(float framesPerSecond) +{ + if (framesPerSecond != fFrameRate) { + fFrameRate = framesPerSecond; + NotifyFPSChanged(fFrameRate); + } +} + + +float +PlaybackManager::FramesPerSecond() const +{ + return fFrameRate; +} + + +void +PlaybackManager::FrameDropped() const +{ + NotifyFrameDropped(); +} + + +void +PlaybackManager::DurationChanged() +{ + if (!_LastState()) + return; + int32 frameCount = Duration(); + if (frameCount != _LastState()->frame_count) { + PlayingState* state = new PlayingState(*_LastState()); + state->frame_count = frameCount; + state->max_frame_count = frameCount; + + _PushState(state, true); + } +} + +/*! Creates a new playing state that equals the previous one aside from + its current frame which is set to /frame/. + The new state will be activated as soon as possible. */ +void +PlaybackManager::SetCurrentFrame(int64 frame) +{ + PlayingState* newState = new PlayingState(*_LastState()); + newState->current_frame = frame; + _PushState(newState, false); +} + + +/*! Creates a new playing state that equals the previous one aside from + its playing mode which is set to /mode/. + The new state will be activated as soon as possible. + If /continuePlaying/ is true and the new state is a `not stopped' + state, playing continues at the frame the last state reaches when the + new state becomes active (or the next frame in the playing range). + If /continuePlaying/ is false, playing starts at the beginning of the + playing range (taking the playing direction into consideration). */ +void +PlaybackManager::SetPlayMode(int32 mode, bool continuePlaying) +{ + PlayingState* newState = new PlayingState(*_LastState()); + newState->play_mode = mode; + // Jump to the playing start frame if we should not continue, where we + // stop. + if (!continuePlaying && !(_PlayingDirectionFor(newState) == 0)) + newState->current_frame = _PlayingStartFrameFor(newState); + _PushState(newState, continuePlaying); + NotifyPlayModeChanged(mode); +} + + +/*! Creates a new playing state that equals the previous one aside from + its loop mode which is set to /mode/. + The new state will be activated as soon as possible. + If /continuePlaying/ is true and the new state is a `not stopped' + state, playing continues at the frame the last state reaches when the + new state becomes active (or the next frame in the playing range). + If /continuePlaying/ is false, playing starts at the beginning of the + playing range (taking the playing direction into consideration). */ +void +PlaybackManager::SetLoopMode(int32 mode, bool continuePlaying) +{ + PlayingState* newState = new PlayingState(*_LastState()); + newState->loop_mode = mode; + // Jump to the playing start frame if we should not continue, where we + // stop. + if (!continuePlaying && !(_PlayingDirectionFor(newState) == 0)) + newState->current_frame = _PlayingStartFrameFor(newState); + _PushState(newState, continuePlaying); + NotifyLoopModeChanged(mode); +} + + +/* Creates a new playing state that equals the previous one aside from + its looping enabled flag which is set to /enabled/. + The new state will be activated as soon as possible. + If /continuePlaying/ is true and the new state is a `not stopped' + state, playing continues at the frame the last state reaches when the + new state becomes active (or the next frame in the playing range). + If /continuePlaying/ is false, playing starts at the beginning of the + playing range (taking the playing direction into consideration). */ +void +PlaybackManager::SetLoopingEnabled(bool enabled, bool continuePlaying) +{ + PlayingState* newState = new PlayingState(*_LastState()); + newState->looping_enabled = enabled; + // Jump to the playing start frame if we should not continue, where we + // stop. + if (!continuePlaying && !(_PlayingDirectionFor(newState) == 0)) + newState->current_frame = _PlayingStartFrameFor(newState); + _PushState(newState, continuePlaying); + NotifyLoopingEnabledChanged(enabled); +} + + +void +PlaybackManager::SetSpeed(float speed) +{ + SpeedInfo* lastSpeed = _LastSpeedInfo(); + if (speed != lastSpeed->set_speed) { + SpeedInfo* info = new SpeedInfo(*lastSpeed); + info->activation_frame = NextFrame(); + info->set_speed = speed; + if (_PlayingDirectionFor(_StateAtFrame(info->activation_frame)) != 0) + info->speed = info->set_speed; + else + info->speed = 1.0; + _PushSpeedInfo(info); + } +} + + +// #pragma mark - + + +/*! Returns the first frame at which a new playing state could become active, + that is the first frame for that neither the audio nor the video producer + have fetched data.*/ +int64 +PlaybackManager::NextFrame() const +{ + return FrameForTime(max((bigtime_t)fCurrentAudioTime, + (bigtime_t)fCurrentVideoTime) - 1) + 1; +} + + +//! Returns the Playlist frame for NextFrame(). +int64 +PlaybackManager::NextPlaylistFrame() const +{ + return PlaylistFrameAtFrame(NextFrame()); +} + + +int64 +PlaybackManager::FirstPlaybackRangeFrame() +{ + PlayingState* state = _StateAtFrame(CurrentFrame()); + switch (state->loop_mode) { + case LOOPING_RANGE: + return state->start_frame; + case LOOPING_SELECTION: + // TODO: ... + return 0; + case LOOPING_VISIBLE: + return state->first_visible_frame; + + case LOOPING_ALL: + default: + return 0; + } +} + + +int64 +PlaybackManager::LastPlaybackRangeFrame() +{ + PlayingState* state = _StateAtFrame(CurrentFrame()); + switch (state->loop_mode) { + case LOOPING_RANGE: + return state->end_frame; + case LOOPING_SELECTION: + // TODO: ... + return state->frame_count - 1; + case LOOPING_VISIBLE: + return state->last_visible_frame; + + case LOOPING_ALL: + default: + return state->frame_count - 1; + } +} + + +// #pragma mark - + + +int64 +PlaybackManager::StartFrameAtFrame(int64 frame) +{ + return _StateAtFrame(frame)->start_frame; +} + + +int64 +PlaybackManager::StartFrameAtTime(bigtime_t time) +{ + return _StateAtTime(time)->start_frame; +} + + +int64 +PlaybackManager::EndFrameAtFrame(int64 frame) +{ + return _StateAtTime(frame)->end_frame; +} + + +int64 +PlaybackManager::EndFrameAtTime(bigtime_t time) +{ + return _StateAtTime(time)->end_frame; +} + + +int64 +PlaybackManager::FrameCountAtFrame(int64 frame) +{ + return _StateAtTime(frame)->frame_count; +} + + +int64 +PlaybackManager::FrameCountAtTime(bigtime_t time) +{ + return _StateAtTime(time)->frame_count; +} + + +int32 +PlaybackManager::PlayModeAtFrame(int64 frame) +{ + return _StateAtTime(frame)->play_mode; +} + + +int32 +PlaybackManager::PlayModeAtTime(bigtime_t time) +{ + return _StateAtTime(time)->play_mode; +} + + +int32 +PlaybackManager::LoopModeAtFrame(int64 frame) +{ + return _StateAtTime(frame)->loop_mode; +} + + +int32 +PlaybackManager::LoopModeAtTime(bigtime_t time) +{ + return _StateAtTime(time)->loop_mode; +} + + +/*! Returns which Playlist frame should be displayed at (performance) video + frame /frame/. Additionally the playing direction (0, 1, -1) is returned, + as well as if a new playing state becomes active with this frame. + A new playing state is installed, if either some data directly concerning + the playing (play mode, loop mode, playing ranges, selection...) or the + Playlist has changed. */ +int64 +PlaybackManager::PlaylistFrameAtFrame(int64 frame, int32& playingDirection, + bool& newState) const +{ +//TRACE("PlaybackManager::PlaylistFrameAtFrame(%lld)\n", frame); + PlayingState* state = _StateAtFrame(frame); + newState = (state->activation_frame == frame); + playingDirection = _PlayingDirectionFor(state); +//TRACE("playing state: activation frame: %lld, current frame: %lld, dir: %ld\n", +//state->activation_frame, state->current_frame, state->play_mode); + // The first part of the index calculation is invariable for a state. We + // could add it to the state data. + int64 result = state->current_frame; + if (playingDirection != 0) { +// int64 index = _RangeFrameForFrame(state, state->current_frame) + int64 index = state->range_index + + (frame - state->activation_frame) * playingDirection; + result = _FrameForRangeFrame(state, index); + } +//TRACE("PlaybackManager::PlaylistFrameAtFrame() done: %lld\n", result); + return result; +} + + +int64 +PlaybackManager::PlaylistFrameAtFrame(int64 frame, int32& playingDirection) const +{ + bool newState; + return PlaylistFrameAtFrame(frame, playingDirection, newState); +} + + +int64 +PlaybackManager::PlaylistFrameAtFrame(int64 frame) const +{ + bool newState; + int32 playingDirection; + return PlaylistFrameAtFrame(frame, playingDirection, newState); +} + + +/*! Returns the index of the next frame after /startFrame/ at which a + playing state or speed change occurs or /endFrame/, if this happens to be + earlier. */ +int64 +PlaybackManager::NextChangeFrame(int64 startFrame, int64 endFrame) const +{ + int32 startIndex = _IndexForFrame(startFrame); + int32 endIndex = _IndexForFrame(endFrame); + if (startIndex < endIndex) + endFrame = _StateAt(startIndex + 1)->activation_frame; + startIndex = _SpeedInfoIndexForFrame(startFrame); + endIndex = _SpeedInfoIndexForFrame(endFrame); + if (startIndex < endIndex) + endFrame = _SpeedInfoAt(startIndex + 1)->activation_frame; + return endFrame; +} + + +/*! Returns the next time after /startTime/ at which a playing state or + speed change occurs or /endTime/, if this happens to be earlier. */ +bigtime_t +PlaybackManager::NextChangeTime(bigtime_t startTime, bigtime_t endTime) const +{ + int32 startIndex = _IndexForTime(startTime); + int32 endIndex = _IndexForTime(endTime); + if (startIndex < endIndex) + endTime = TimeForFrame(_StateAt(startIndex + 1)->activation_frame); + startIndex = _SpeedInfoIndexForTime(startTime); + endIndex = _SpeedInfoIndexForTime(endTime); + if (startIndex < endIndex) + endTime = TimeForFrame(_SpeedInfoAt(startIndex + 1)->activation_frame); + return endTime; +} + + +/*! Returns a contiguous Playlist frame interval for a given frame interval. + The returned interval may be smaller than the supplied one. Therefore + the supplied /endFrame/ is adjusted. + The value written to /xEndFrame/ is the first frame that doesn't belong + to the interval. /playingDirection/ may either be -1 for backward, + 1 for forward or 0 for not playing. */ +void +PlaybackManager::GetPlaylistFrameInterval(int64 startFrame, int64& endFrame, + int64& xStartFrame, int64& xEndFrame, int32& playingDirection) const +{ + // limit the interval to one state and speed info + endFrame = NextChangeFrame(startFrame, endFrame); + // init return values + xStartFrame = PlaylistFrameAtFrame(startFrame); + xEndFrame = xStartFrame; + playingDirection = _PlayingDirectionFor(_StateAtFrame(startFrame)); + // Step through the interval and check whether the respective Playlist + // frames belong to the Playlist interval. + bool endOfInterval = false; + int64 intervalLength = 0; + while (startFrame < endFrame && !endOfInterval) { + intervalLength++; + startFrame++; + xEndFrame += playingDirection; + endOfInterval = (PlaylistFrameAtFrame(startFrame) != xEndFrame); + } + // order the interval bounds, if playing backwards + if (xStartFrame > xEndFrame) { + swap(xStartFrame, xEndFrame); + xStartFrame++; + xEndFrame++; + } +} + + +/*! The same as GetPlaylistFrameInterval() just for time instead of frame + intervals. Note, that /startTime/ and /endTime/ measure + performance times whereas /xStartTime/ and /xEndTime/ specifies an + Playlist time interval. In general it does not hold + xEndTime - xStartTime == endTime - startTime, even if playing (think + of a playing speed != 1.0). */ +void +PlaybackManager::GetPlaylistTimeInterval(bigtime_t startTime, + bigtime_t& endTime, bigtime_t& xStartTime, bigtime_t& xEndTime, + float& playingSpeed) const +{ + // Get the frames that bound the given time interval. The end frame might + // be greater than necessary, but that doesn't harm. + int64 startFrame = FrameForTime(startTime); + int64 endFrame = FrameForTime(endTime) + 1; + SpeedInfo* info = _SpeedInfoForFrame(startFrame); + // Get the Playlist frame interval that belongs to the frame interval. + int64 xStartFrame; + int64 xEndFrame; + int32 playingDirection; + GetPlaylistFrameInterval(startFrame, endFrame, xStartFrame, xEndFrame, + playingDirection); + // Calculate the performance time interval end/length. + endTime = min(endTime, TimeForFrame(endFrame)); + bigtime_t intervalLength = endTime - startTime; + // Finally determine the time bounds for the Playlist interval (depending + // on the playing direction). + switch (playingDirection) { + // forward + case 1: + { +// xStartTime = PlaylistTimeForFrame(xStartFrame) +// + startTime - TimeForFrame(startFrame); +// xEndTime = xStartTime + intervalLength; + +// TODO: The current method does not handle the times the same way. +// It may happen, that for the same performance time different +// Playlist times (within a frame) are returned when passing it +// one time as a start time and another time as an end time. + xStartTime = PlaylistTimeForFrame(xStartFrame) + + bigtime_t(double(startTime - TimeForFrame(startFrame)) + * info->speed); + xEndTime = xStartTime + + bigtime_t((double)intervalLength * info->speed); + break; + } + // backward + case -1: + { +// xEndTime = PlaylistTimeForFrame(xEndFrame) +// - startTime + TimeForFrame(startFrame); +// xStartTime = xEndTime - intervalLength; + + xEndTime = PlaylistTimeForFrame(xEndFrame) + - bigtime_t(double(startTime - TimeForFrame(startFrame)) + * info->speed); + xStartTime = xEndTime + - bigtime_t((double)intervalLength * info->speed); + break; + } + // not playing + case 0: + default: + xStartTime = PlaylistTimeForFrame(xStartFrame); + xEndTime = xStartTime; + break; + } + playingSpeed = (float)playingDirection * info->speed; +} + + +/*! Returns the frame that is being performed at the supplied time. + It holds TimeForFrame(frame) <= time < TimeForFrame(frame + 1). */ +int64 +PlaybackManager::FrameForTime(bigtime_t time) const +{ +//TRACE("PlaybackManager::FrameForTime(%lld)\n", time); +// return (int64)((double)time * (double)fFrameRate / 1000000.0); + // In order to avoid problems caused by rounding errors, we check + // if for the resulting frame holds + // TimeForFrame(frame) <= time < TimeForFrame(frame + 1). +// int64 frame = (int64)((double)time * (double)fFrameRate / 1000000.0); + SpeedInfo* info = _SpeedInfoForTime(time); +if (!info) { + fprintf(stderr, "PlaybackManager::FrameForTime() - no SpeedInfo!\n"); + return 0; +} + int64 frame = (int64)(((double)time - info->activation_time) + * (double)fFrameRate * info->speed / 1000000.0) + + info->activation_frame; + if (TimeForFrame(frame) > time) + frame--; + else if (TimeForFrame(frame + 1) <= time) + frame++; +//TRACE("PlaybackManager::FrameForTime() done: %lld\n", frame); +//fprintf(stderr, "PlaybackManager::FrameForTime(%lld): %lld\n", time, frame); + return frame; +} + + +/*! Returns the time at which the supplied frame should be performed (started + to be performed). */ +bigtime_t +PlaybackManager::TimeForFrame(int64 frame) const +{ +// return (bigtime_t)((double)frame * 1000000.0 / (double)fFrameRate); + SpeedInfo* info = _SpeedInfoForFrame(frame); +if (!info) { + fprintf(stderr, "PlaybackManager::TimeForFrame() - no SpeedInfo!\n"); + return 0; +} +// return (bigtime_t)((double)(frame - info->activation_frame) * 1000000.0 +// / ((double)fFrameRate * info->speed)) +// + info->activation_time; +bigtime_t result = (bigtime_t)((double)(frame - info->activation_frame) * 1000000.0 +/ ((double)fFrameRate * info->speed)) + info->activation_time; +//fprintf(stderr, "PlaybackManager::TimeForFrame(%lld): %lld\n", frame, result); +return result; +} + + +/*! Returns the Playlist frame for an Playlist time. + It holds PlaylistTimeForFrame(frame) <= time < + PlaylistTimeForFrame(frame + 1). */ +int64 +PlaybackManager::PlaylistFrameForTime(bigtime_t time) const +{ + // In order to avoid problems caused by rounding errors, we check + // if for the resulting frame holds + // PlaylistTimeForFrame(frame) <= time < PlaylistTimeForFrame(frame + 1). + int64 frame = (int64)((double)time * (double)fFrameRate / 1000000.0); + if (TimeForFrame(frame) > time) + frame--; + else if (TimeForFrame(frame + 1) <= time) + frame++; + return frame; +} + + +//! Returns the Playlist start time for an Playlist frame. +bigtime_t +PlaybackManager::PlaylistTimeForFrame(int64 frame) const +{ + return (bigtime_t)((double)frame * 1000000.0 / (double)fFrameRate); +} + + +//! To be called when done with all activities concerning audio before /time/. +void +PlaybackManager::SetCurrentAudioTime(bigtime_t time) +{ +TRACE("PlaybackManager::SetCurrentAudioTime(%lld)\n", time); + bigtime_t lastFrameTime = _TimeForLastFrame(); + fCurrentAudioTime = time; +// _UpdateStates(); + bigtime_t newLastFrameTime = _TimeForLastFrame(); + if (lastFrameTime != newLastFrameTime) { + bigtime_t eventTime = RealTimeForTime(newLastFrameTime); + EventQueue::Default().AddEvent(new MessageEvent(eventTime, this)); + _CheckStopPlaying(); + } +} + + +//! To be called when done with all activities concerning video before /frame/. +void +PlaybackManager::SetCurrentVideoFrame(int64 frame) +{ + SetCurrentVideoTime(TimeForFrame(frame)); +} + + +//! To be called when done with all activities concerning video before /time/. +void +PlaybackManager::SetCurrentVideoTime(bigtime_t time) +{ +TRACE("PlaybackManager::SetCurrentVideoTime(%lld)\n", time); + bigtime_t lastFrameTime = _TimeForLastFrame(); + fCurrentVideoTime = time; +// _UpdateStates(); + bigtime_t newLastFrameTime = _TimeForLastFrame(); + if (lastFrameTime != newLastFrameTime) { + bigtime_t eventTime = RealTimeForTime(newLastFrameTime); + EventQueue::Default().AddEvent(new MessageEvent(eventTime, this)); + _CheckStopPlaying(); + } +} + + +//! To be called as soon as video frame /frame/ is being performed. +void +PlaybackManager::SetPerformanceFrame(int64 frame) +{ + SetPerformanceFrame(TimeForFrame(frame)); +} + + +/*! Similar to SetPerformanceTime() just with a time instead of a frame + argument. */ +void +PlaybackManager::SetPerformanceTime(bigtime_t time) +{ + int32 oldCurrentFrame = CurrentFrame(); + fPerformanceTime = time; + _UpdateStates(); + _UpdateSpeedInfos(); + int32 currentFrame = CurrentFrame(); + +//printf("PlaybackManager::SetPerformanceTime(%lld): %ld -> %ld\n", +// time, oldCurrentFrame, currentFrame); + + if (currentFrame != oldCurrentFrame) + NotifyCurrentFrameChanged(currentFrame); +} + + +// #pragma mark - Listeners + + +void +PlaybackManager::AddListener(PlaybackListener* listener) +{ + if (!listener) + return; + + if (!Lock()) + return; + + if (!fListeners.HasItem(listener) && fListeners.AddItem(listener)) { + // bring listener up2date, if we have been initialized + if (_LastState()) { + listener->PlayModeChanged(PlayMode()); + listener->LoopModeChanged(LoopMode()); + listener->LoopingEnabledChanged(IsLoopingEnabled()); + listener->VideoBoundsChanged(VideoBounds()); + listener->FramesPerSecondChanged(FramesPerSecond()); + listener->SpeedChanged(Speed()); + listener->CurrentFrameChanged(CurrentFrame()); + } + } + + Unlock(); +} + + +void +PlaybackManager::RemoveListener(PlaybackListener* listener) +{ + if (listener && Lock()) { + fListeners.RemoveItem(listener); + Unlock(); + } +} + + +void +PlaybackManager::NotifyPlayModeChanged(int32 mode) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->PlayModeChanged(mode); + } +} + + +void +PlaybackManager::NotifyLoopModeChanged(int32 mode) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->LoopModeChanged(mode); + } +} + + +void +PlaybackManager::NotifyLoopingEnabledChanged(bool enabled) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->LoopingEnabledChanged(enabled); + } +} + + +void +PlaybackManager::NotifyVideoBoundsChanged(BRect bounds) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->VideoBoundsChanged(bounds); + } +} + + +void +PlaybackManager::NotifyFPSChanged(float fps) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->FramesPerSecondChanged(fps); + } +} + + +void +PlaybackManager::NotifyCurrentFrameChanged(int32 frame) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->CurrentFrameChanged(frame); + } +} + + +void +PlaybackManager::NotifySpeedChanged(float speed) const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->SpeedChanged(speed); + } +} + + +void +PlaybackManager::NotifyFrameDropped() const +{ + for (int32 i = 0; + PlaybackListener* listener = (PlaybackListener*)fListeners.ItemAt(i); + i++) { + listener->FrameDropped(); + } +} + + +void +PlaybackManager::NotifyStopFrameReached() const +{ + // not currently implemented in PlaybackListener interface +} + + +void +PlaybackManager::PrintState(PlayingState* state) +{ + TRACE("state: activation frame: %lld, current frame: %lld, " + "start frame: %lld, end frame: %lld, frame count: %lld, " + "first visible: %lld, last visible: %lld, selection (...), " + "play mode: %ld, loop mode: %ld\n", + state->activation_frame, + state->current_frame, + state->start_frame, + state->end_frame, + state->frame_count, + state->first_visible_frame, + state->last_visible_frame, +// state->selection, + state->play_mode, + state->loop_mode); +} + + +void +PlaybackManager::PrintStateAtFrame(int64 frame) +{ + TRACE("frame %lld: ", frame); + PrintState(_StateAtFrame(frame)); +} + + +/*! Appends the supplied state to the list of states. If the state would + become active at the same time as _LastState() the latter is removed + and deleted. However, the activation time for the new state is adjusted, + so that it is >= that of the last state and >= the current audio and + video time. If the additional parameter /adjustCurrentFrame/ is true, + the new state's current frame is tried to be set to the frame that is + reached at the time the state will become active. In every case + it is ensured that the current frame lies within the playing range + (if playing). */ +void +PlaybackManager::_PushState(PlayingState* state, bool adjustCurrentFrame) +{ +// if (!_LastState()) +// debugger("PlaybackManager::_PushState() used before Init()\n"); + +TRACE("PlaybackManager::_PushState()\n"); + if (state) { + // unset fStopPlayingFrame + int64 oldStopPlayingFrame = fStopPlayingFrame; + fStopPlayingFrame = -1; + // get last state + PlayingState* lastState = _LastState(); + int64 activationFrame = max(max(state->activation_frame, + lastState->activation_frame), + NextFrame()); +TRACE(" state activation frame: %lld, last state activation frame: %lld, " +"NextFrame(): %lld\n", state->activation_frame, lastState->activation_frame, +NextFrame()); + + int64 currentFrame = 0; + // remember the current frame, if necessary + if (adjustCurrentFrame) + currentFrame = PlaylistFrameAtFrame(activationFrame); + // check whether it is active + // (NOTE: We may want to keep the last state, if it is not active, + // but then the new state should become active after the last one. + // Thus we had to replace `NextFrame()' with `activationFrame'.) + if (lastState->activation_frame >= NextFrame()) { + // it isn't -- remove it + fStates.RemoveItem(fStates.CountItems() - 1); +TRACE("deleting last \n"); +PrintState(lastState); + delete lastState; + } else { + // it is -- keep it + } + // adjust the new state's current frame and activation frame + if (adjustCurrentFrame) + state->current_frame = currentFrame; + int32 playingDirection = _PlayingDirectionFor(state); + if (playingDirection != 0) { + state->current_frame + = _NextFrameInRange(state, state->current_frame); + } else { + // If not playing, we check at least, if the current frame lies + // within the interval [0, max_frame_count). + if (state->current_frame >= state->max_frame_count) + state->current_frame = state->max_frame_count - 1; + if (state->current_frame < 0) + state->current_frame = 0; + } + state->range_index = _RangeFrameForFrame(state, state->current_frame); + state->activation_frame = activationFrame; + fStates.AddItem(state); +PrintState(state); +TRACE("_PushState: state count: %ld\n", fStates.CountItems()); + // push a new speed info + SpeedInfo* speedInfo = new SpeedInfo(*_LastSpeedInfo()); + if (playingDirection == 0) + speedInfo->speed = 1.0; + else + speedInfo->speed = speedInfo->set_speed; + speedInfo->activation_frame = state->activation_frame; + _PushSpeedInfo(speedInfo); + // If the new state is a playing state and looping is turned off, + // determine when playing shall stop. + if (playingDirection != 0 && !state->looping_enabled) { + int64 startFrame, endFrame, frameCount; + _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount); + if (playingDirection == -1) + swap(startFrame, endFrame); + // If we shall stop at the frame we start, set the current frame + // to the beginning of the range. + // We have to take care, since this state may equal the one + // before (or probably differs in just one (unimportant) + // parameter). This happens for instance, if the user changes the + // data or start/end frame... while playing. In this case setting + // the current frame to the start frame is unwanted. Therefore + // we check whether the previous state was intended to stop + // at the activation frame of this state. + if (oldStopPlayingFrame != state->activation_frame + && state->current_frame == endFrame && frameCount > 1) { + state->current_frame = startFrame; + state->range_index + = _RangeFrameForFrame(state, state->current_frame); + } + if (playingDirection == 1) { // forward + fStopPlayingFrame = state->activation_frame + + frameCount - state->range_index - 1; + } else { // backwards + fStopPlayingFrame = state->activation_frame + + state->range_index; + } + _CheckStopPlaying(); + } + } +TRACE("PlaybackManager::_PushState() done\n"); +} + + +/*! Removes and deletes all states that are obsolete, that is which stopped + being active at or before the current audio and video time. */ +void +PlaybackManager::_UpdateStates() +{ +// int32 firstActive = min(_IndexForTime(fCurrentAudioTime), +// _IndexForTime(fCurrentVideoTime)); + // Performance time should always be the least one. + int32 firstActive = _IndexForTime(fPerformanceTime); +//TRACE("firstActive: %ld, numStates: %ld\n", firstActive, fStates.CountItems()); + for (int32 i = 0; i < firstActive; i++) + delete _StateAt(i); + if (firstActive > 0) +{ + fStates.RemoveItems(0, firstActive); +TRACE("_UpdateStates: states removed: %ld, state count: %ld\n", +firstActive, fStates.CountItems()); +} +} + + +/*! Returns the index of the state, that is active at frame /frame/. + The index of the first frame (0) is returned, if /frame/ is even less + than the frame at which this state becomes active. */ +int32 +PlaybackManager::_IndexForFrame(int64 frame) const +{ + int32 index = 0; + PlayingState* state; + while (((state = _StateAt(index + 1))) && state->activation_frame <= frame) + index++; + return index; +} + +/*! Returns the index of the state, that is active at time /time/. + The index of the first frame (0) is returned, if /time/ is even less + than the time at which this state becomes active. */ +int32 +PlaybackManager::_IndexForTime(bigtime_t time) const +{ + return _IndexForFrame(FrameForTime(time)); +} + + +//! Returns the state that reflects the most recent changes. +PlaybackManager::PlayingState* +PlaybackManager::_LastState() const +{ + return _StateAt(fStates.CountItems() - 1); +} + + +PlaybackManager::PlayingState* +PlaybackManager::_StateAt(int32 index) const +{ + return (PlayingState*)fStates.ItemAt(index); +} + + +PlaybackManager::PlayingState* +PlaybackManager::_StateAtFrame(int64 frame) const +{ + return _StateAt(_IndexForFrame(frame)); +} + + +PlaybackManager::PlayingState* +PlaybackManager::_StateAtTime(bigtime_t time) const +{ + return _StateAt(_IndexForTime(time)); +} + + +int32 +PlaybackManager::_PlayingDirectionFor(int32 playingMode) +{ + int32 direction = 0; + switch (playingMode) { + case MODE_PLAYING_FORWARD: + direction = 1; + break; + case MODE_PLAYING_BACKWARD: + direction = -1; + break; + case MODE_PLAYING_PAUSED_FORWARD: + case MODE_PLAYING_PAUSED_BACKWARD: + break; + } + return direction; +} + + +int32 +PlaybackManager::_PlayingDirectionFor(PlayingState* state) +{ + return _PlayingDirectionFor(state->play_mode); +} + + +/*! Returns the Playlist frame range that bounds the playing range of a given + state. + \a startFrame is the lower and \a endFrame the upper bound of the range, + and \a frameCount the number of frames in the range; it is guaranteed to + be >= 1, even for an empty selection. */ +void +PlaybackManager::_GetPlayingBoundsFor(PlayingState* state, int64& startFrame, + int64& endFrame, int64& frameCount) +{ + switch (state->loop_mode) { + case LOOPING_ALL: + startFrame = 0; + endFrame = max(startFrame, state->frame_count - 1); + frameCount = endFrame - startFrame + 1; + break; + case LOOPING_RANGE: + startFrame = state->start_frame; + endFrame = state->end_frame; + frameCount = endFrame - startFrame + 1; + break; +// case LOOPING_SELECTION: +// if (!state->selection.IsEmpty()) { +// startFrame = state->selection.FirstIndex(); +// endFrame = state->selection.LastIndex(); +// frameCount = state->selection.CountIndices(); +//TRACE(" LOOPING_SELECTION: %lld - %lld (%lld)\n", startFrame, endFrame, +//frameCount); +// } else { +// startFrame = state->current_frame; +// endFrame = state->current_frame; +// frameCount = 1; +// } +// break; + case LOOPING_VISIBLE: + startFrame = state->first_visible_frame; + endFrame = state->last_visible_frame; + frameCount = endFrame - startFrame + 1; + break; + } +} + + +/*! Returns the frame at which playing would start for a given playing + state. If the playing mode for the supplied state specifies a stopped + or undefined mode, the result is the state's current frame. */ +int64 +PlaybackManager::_PlayingStartFrameFor(PlayingState* state) +{ + int64 startFrame, endFrame, frameCount, frame = 0; + _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount); + switch (_PlayingDirectionFor(state)) { + case -1: + frame = endFrame; + break; + case 1: + frame = startFrame; + break; + default: + frame = state->current_frame; + break; + } + return frame; +} + + +/*! Returns the frame at which playing would end for a given playing + state. If the playing mode for the supplied state specifies a stopped + or undefined mode, the result is the state's current frame. */ +int64 +PlaybackManager::_PlayingEndFrameFor(PlayingState* state) +{ + int64 startFrame, endFrame, frameCount, frame = 0; + _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount); + switch (_PlayingDirectionFor(state)) { + case -1: + frame = startFrame; + break; + case 1: + frame = endFrame; + break; + default: + frame = state->current_frame; + break; + } + return frame; +} + + +/*! Returns the index that the supplied frame has within a playing range. + If the state specifies a not-playing mode, 0 is returned. The supplied + frame has to lie within the bounds of the playing range, but it doesn't + need to be contained, e.g. if the range is not contiguous. In this case + the index of the next frame within the range (in playing direction) is + returned. */ +int64 +PlaybackManager::_RangeFrameForFrame(PlayingState* state, int64 frame) +{ +TRACE("PlaybackManager::_RangeFrameForFrame(%lld)\n", frame); + int64 startFrame, endFrame, frameCount; + int64 index = 0; + _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount); +TRACE(" start frame: %lld, end frame: %lld, frame count: %lld\n", +startFrame, endFrame, frameCount); + switch (state->loop_mode) { + case LOOPING_ALL: + case LOOPING_RANGE: + case LOOPING_VISIBLE: + index = frame - startFrame; + break; + } +TRACE("PlaybackManager::_RangeFrameForFrame() done: %lld\n", index); + return index; +} + + +/*! Returns the Playlist frame for a playing range index. /index/ doesn't need + to be in the playing range -- it is mapped into. */ +int64 +PlaybackManager::_FrameForRangeFrame(PlayingState* state, int64 index) +{ +TRACE("PlaybackManager::_FrameForRangeFrame(%lld)\n", index); + int64 startFrame, endFrame, frameCount; + _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount); +TRACE(" frame range: %lld - %lld, count: %lld\n", startFrame, endFrame, +frameCount); + // map the index into the index interval of the playing range + if (frameCount > 0) + index = (index % frameCount + frameCount) % frameCount; + else + index = 0; + // get the frame for the index + int32 frame = startFrame; + switch (state->loop_mode) { + case LOOPING_ALL: + case LOOPING_RANGE: + case LOOPING_VISIBLE: + frame = startFrame + index; + break; + } +TRACE("PlaybackManager::_FrameForRangeFrame() done: %ld\n", frame); + return frame; +} + + +/*! Given an arbitrary Playlist frame this function returns the next frame within + the playing range for the supplied playing state. */ +int64 +PlaybackManager::_NextFrameInRange(PlayingState* state, int64 frame) +{ + int64 newFrame = frame; + int64 startFrame, endFrame, frameCount; + _GetPlayingBoundsFor(state, startFrame, endFrame, frameCount); + if (frame < startFrame || frame > endFrame) + newFrame = _PlayingStartFrameFor(state); + else { + int64 index = _RangeFrameForFrame(state, frame); + newFrame = _FrameForRangeFrame(state, index); + if (newFrame > frame && _PlayingDirectionFor(state) == -1) + newFrame = _FrameForRangeFrame(state, index - 1); + } + return newFrame; +} + + +void +PlaybackManager::_PushSpeedInfo(SpeedInfo* info) +{ + if (info) { + // check the last state + if (SpeedInfo* lastSpeed = _LastSpeedInfo()) { + // set the activation time + info->activation_time = TimeForFrame(info->activation_frame); + // Remove the last state, if it won't be activated. + if (lastSpeed->activation_frame == info->activation_frame) { +//fprintf(stderr, " replacing last speed info\n"); + fSpeeds.RemoveItem(lastSpeed); + delete lastSpeed; + } + } + fSpeeds.AddItem(info); +//fprintf(stderr, " speed info pushed: activation frame: %lld, " +//"activation time: %lld, speed: %f\n", info->activation_frame, +//info->activation_time, info->speed); + // ... + } +} + + +PlaybackManager::SpeedInfo* +PlaybackManager::_LastSpeedInfo() const +{ + return (SpeedInfo*)fSpeeds.ItemAt(fSpeeds.CountItems() - 1); +} + + +PlaybackManager::SpeedInfo* +PlaybackManager::_SpeedInfoAt(int32 index) const +{ + return (SpeedInfo*)fSpeeds.ItemAt(index); +} + + +int32 +PlaybackManager::_SpeedInfoIndexForFrame(int64 frame) const +{ + int32 index = 0; + SpeedInfo* info; + while (((info = _SpeedInfoAt(index + 1))) + && info->activation_frame <= frame) { + index++; + } +//fprintf(stderr, "PlaybackManager::_SpeedInfoIndexForFrame(%lld): %ld\n", +//frame, index); + return index; +} + + +int32 +PlaybackManager::_SpeedInfoIndexForTime(bigtime_t time) const +{ + int32 index = 0; + SpeedInfo* info; + while (((info = _SpeedInfoAt(index + 1))) + && info->activation_time <= time) { + index++; + } +//fprintf(stderr, "PlaybackManager::_SpeedInfoIndexForTime(%lld): %ld\n", +//time, index); + return index; +} + + +PlaybackManager::SpeedInfo* +PlaybackManager::_SpeedInfoForFrame(int64 frame) const +{ + return _SpeedInfoAt(_SpeedInfoIndexForFrame(frame)); +} + + +PlaybackManager::SpeedInfo* +PlaybackManager::_SpeedInfoForTime(bigtime_t time) const +{ + return _SpeedInfoAt(_SpeedInfoIndexForTime(time)); +} + + +void +PlaybackManager::_UpdateSpeedInfos() +{ + int32 firstActive = _SpeedInfoIndexForTime(fPerformanceTime); + for (int32 i = 0; i < firstActive; i++) + delete _SpeedInfoAt(i); + if (firstActive > 0) + fSpeeds.RemoveItems(0, firstActive); +//fprintf(stderr, " speed infos 0 - %ld removed\n", firstActive); +} + + +/*! Returns the end time for the last video frame that the video and the + audio producer both have completely processed. */ +bigtime_t +PlaybackManager::_TimeForLastFrame() const +{ + return TimeForFrame(FrameForTime(min((bigtime_t)fCurrentAudioTime, + (bigtime_t)fCurrentVideoTime))); +} + + +/*! Returns the start time for the first video frame for which + neither the video nor the audio producer have fetched data. */ +bigtime_t +PlaybackManager::_TimeForNextFrame() const +{ + return TimeForFrame(NextFrame()); +} + + +void +PlaybackManager::_CheckStopPlaying() +{ + if (fStopPlayingFrame >= 0 && fStopPlayingFrame <= NextFrame()) { + StopPlaying(); + NotifyStopFrameReached(); + } +} + diff --git a/src/apps/mediaplayer/media_node_framework/PlaybackManager.h b/src/apps/mediaplayer/media_node_framework/PlaybackManager.h new file mode 100644 index 0000000000..adc1133f47 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/PlaybackManager.h @@ -0,0 +1,234 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ + +/*! This class controls our playback + Note: Add/RemoveListener() are the only methods that lock the object + themselves. All other methods need it to be locked before. +*/ +#ifndef PLAYBACK_MANAGER_H +#define PLAYBACK_MANAGER_H + + +#include +#include +#include + + +class PlaybackListener; + + +enum { + MODE_PLAYING_FORWARD = 1, + MODE_PLAYING_BACKWARD = 2, + MODE_PLAYING_PAUSED_FORWARD = -1, + MODE_PLAYING_PAUSED_BACKWARD = -2, +}; + +enum { + LOOPING_ALL = 0, + LOOPING_RANGE = 1, + LOOPING_SELECTION = 2, + LOOPING_VISIBLE = 3, +}; + +enum { + MSG_PLAYBACK_FORCE_UPDATE = 'pbfu', + MSG_PLAYBACK_SET_RANGE = 'pbsr', + MSG_PLAYBACK_SET_VISIBLE = 'pbsv', + MSG_PLAYBACK_SET_LOOP_MODE = 'pbsl', +}; + + +class PlaybackManager : public BLooper { +private: + struct PlayingState; + struct SpeedInfo; + +public: + PlaybackManager(); + virtual ~PlaybackManager(); + + void Init(float frameRate, + int32 loopingMode = LOOPING_ALL, + bool loopingEnabled = true, + float speed = 1.0, + int32 playMode = MODE_PLAYING_PAUSED_FORWARD, + int32 currentFrame = 0); + void Cleanup(); + + // BHandler interface + virtual void MessageReceived(BMessage* message); + + void StartPlaying(bool atBeginning = false); + void StopPlaying(); + void TogglePlaying(bool atBeginning = false); + void PausePlaying(); + bool IsPlaying() const; + + int32 PlayMode() const; + int32 LoopMode() const; + bool IsLoopingEnabled() const; + int32 CurrentFrame() const; + float Speed() const; + + virtual void SetFramesPerSecond(float framesPerSecond); + float FramesPerSecond() const; + + virtual BRect VideoBounds() const = 0; + + virtual void FrameDropped() const; + + void DurationChanged(); + virtual int64 Duration() = 0; + + virtual void SetVolume(float percent) = 0; + + // playing state manipulation + void SetCurrentFrame(int64 frame); + virtual void SetPlayMode(int32 mode, + bool continuePlaying = true); + void SetLoopMode(int32 mode, + bool continuePlaying = true); + void SetLoopingEnabled(bool enabled, + bool continuePlaying = true); + void SetSpeed(float speed); + + // playing state change info + int64 NextFrame() const; + int64 NextPlaylistFrame() const; + + int64 FirstPlaybackRangeFrame(); + int64 LastPlaybackRangeFrame(); + + // playing state access + int64 StartFrameAtFrame(int64 frame); + int64 StartFrameAtTime(bigtime_t time); + int64 EndFrameAtFrame(int64 frame); + int64 EndFrameAtTime(bigtime_t time); + int64 FrameCountAtFrame(int64 frame); + int64 FrameCountAtTime(bigtime_t time); + int32 PlayModeAtFrame(int64 frame); + int32 PlayModeAtTime(bigtime_t time); + int32 LoopModeAtFrame(int64 frame); + int32 LoopModeAtTime(bigtime_t time); + // ... + + // playing frame/time/interval info + int64 PlaylistFrameAtFrame(int64 frame, + int32& playingDirection, + bool& newState) const; + int64 PlaylistFrameAtFrame(int64 frame, + int32& playingDirection) const; + int64 PlaylistFrameAtFrame(int64 frame) const; + int64 NextChangeFrame(int64 startFrame, + int64 endFrame) const; + bigtime_t NextChangeTime(bigtime_t startTime, + bigtime_t endTime) const; + void GetPlaylistFrameInterval( + int64 startFrame, int64& endFrame, + int64& xStartFrame, int64& xEndFrame, + int32& playingDirection) const; + + // PlaybackManagerInterface + virtual void GetPlaylistTimeInterval( + bigtime_t startTime, bigtime_t& endTime, + bigtime_t& xStartTime, bigtime_t& xEndTime, + float& playingSpeed) const; + + // conversion: video frame <-> (performance) time + int64 FrameForTime(bigtime_t time) const; + bigtime_t TimeForFrame(int64 frame) const; + // conversion: (performance) time <-> real time + virtual bigtime_t RealTimeForTime(bigtime_t time) const = 0; + virtual bigtime_t TimeForRealTime(bigtime_t time) const = 0; + // conversion: Playist frame <-> Playlist time + int64 PlaylistFrameForTime(bigtime_t time) const; + bigtime_t PlaylistTimeForFrame(int64 frame) const; + + // to be called by audio/video producers + virtual void SetCurrentAudioTime(bigtime_t time); + void SetCurrentVideoFrame(int64 frame); + void SetCurrentVideoTime(bigtime_t time); + void SetPerformanceFrame(int64 frame); + void SetPerformanceTime(bigtime_t time); + + // listener support + void AddListener(PlaybackListener* listener); + void RemoveListener(PlaybackListener* listener); + + virtual void NotifyPlayModeChanged(int32 mode) const; + virtual void NotifyLoopModeChanged(int32 mode) const; + virtual void NotifyLoopingEnabledChanged( + bool enabled) const; + virtual void NotifyVideoBoundsChanged(BRect bounds) const; + virtual void NotifyFPSChanged(float fps) const; + virtual void NotifyCurrentFrameChanged(int32 frame) const; + virtual void NotifySpeedChanged(float speed) const; + virtual void NotifyFrameDropped() const; + virtual void NotifyStopFrameReached() const; + + // debugging + void PrintState(PlayingState* state); + void PrintStateAtFrame(int64 frame); + + private: + // state management + void _PushState(PlayingState* state, + bool adjustCurrentFrame); + void _UpdateStates(); + int32 _IndexForFrame(int64 frame) const; + int32 _IndexForTime(bigtime_t time) const; + PlayingState* _LastState() const; + PlayingState* _StateAt(int32 index) const; + PlayingState* _StateAtTime(bigtime_t time) const; + PlayingState* _StateAtFrame(int64 frame) const; + + static int32 _PlayingDirectionFor(int32 playingMode); + static int32 _PlayingDirectionFor(PlayingState* state); + static void _GetPlayingBoundsFor(PlayingState* state, + int64& startFrame, + int64& endFrame, + int64& frameCount); + static int64 _PlayingStartFrameFor(PlayingState* state); + static int64 _PlayingEndFrameFor(PlayingState* state); + static int64 _RangeFrameForFrame(PlayingState* state, + int64 frame); + static int64 _FrameForRangeFrame(PlayingState* state, + int64 index); + static int64 _NextFrameInRange(PlayingState* state, + int64 frame); + + // speed management + void _PushSpeedInfo(SpeedInfo* info); + SpeedInfo* _LastSpeedInfo() const; + SpeedInfo* _SpeedInfoAt(int32 index) const; + int32 _SpeedInfoIndexForFrame(int64 frame) const; + int32 _SpeedInfoIndexForTime(bigtime_t time) const; + SpeedInfo* _SpeedInfoForFrame(int64 frame) const; + SpeedInfo* _SpeedInfoForTime(bigtime_t time) const; + void _UpdateSpeedInfos(); + + bigtime_t _TimeForLastFrame() const; + bigtime_t _TimeForNextFrame() const; + + void _CheckStopPlaying(); + + private: + BList fStates; + BList fSpeeds; + volatile bigtime_t fCurrentAudioTime; + volatile bigtime_t fCurrentVideoTime; + volatile bigtime_t fPerformanceTime; + volatile float fFrameRate; // video frame rate + volatile bigtime_t fStopPlayingFrame; // frame at which playing + // shall be stopped, + // disabled: -1 + + BList fListeners; +}; + + +#endif // PLAYBACK_MANAGER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioAdapter.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioAdapter.cpp new file mode 100644 index 0000000000..61fd78addc --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioAdapter.cpp @@ -0,0 +1,110 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#include "AudioAdapter.h" + +#include +#include +#include +#include + +#include + +#include "AudioChannelConverter.h" +#include "AudioFormatConverter.h" +#include "AudioResampler.h" + +using std::nothrow; + +#define TRACE_AUDIO_ADAPTER +#ifdef TRACE_AUDIO_ADAPTER +# define TRACE(x...) printf(x) +#else +# define TRACE(x...) +#endif + + +AudioAdapter::AudioAdapter(AudioReader* source, const media_format& format) + : AudioReader(format), + fSource(source), + fFinalConverter(NULL), + fFormatConverter(NULL), + fChannelConverter(NULL), + fResampler(NULL) +{ + uint32 hostByteOrder + = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + fFormat.u.raw_audio.byte_order = hostByteOrder; + if (source && source->Format().type == B_MEDIA_RAW_AUDIO) { + + if (fFormat.u.raw_audio.format != source->Format().u.raw_audio.format + || source->Format().u.raw_audio.byte_order != hostByteOrder) { + TRACE("AudioAdapter() - using format converter\n"); + fFormatConverter = new (nothrow) AudioFormatConverter(source, + fFormat.u.raw_audio.format, hostByteOrder); + source = fFormatConverter; + } + + if (fFormat.u.raw_audio.frame_rate + != source->Format().u.raw_audio.frame_rate) { + TRACE("AudioAdapter() - using resampler\n"); + fResampler = new (nothrow) AudioResampler(source, + fFormat.u.raw_audio.frame_rate); + source = fResampler; + } + + if (fFormat.u.raw_audio.channel_count + != source->Format().u.raw_audio.channel_count) { + TRACE("AudioAdapter() - using channel converter\n"); + fChannelConverter = new (nothrow) AudioChannelConverter(source, + fFormat); + source = fChannelConverter; + } + + fFinalConverter = source; + } else + fSource = NULL; +} + + +AudioAdapter::~AudioAdapter() +{ + delete fFormatConverter; + delete fChannelConverter; + delete fResampler; +} + + +status_t +AudioAdapter::Read(void* buffer, int64 pos, int64 frames) +{ +// TRACE("AudioAdapter::Read(%p, %Ld, %Ld)\n", buffer, pos, frames); + status_t error = InitCheck(); + if (error != B_OK) + return error; + pos += fOutOffset; + status_t ret = fFinalConverter->Read(buffer, pos, frames); +// TRACE("AudioAdapter::Read() done: %s\n", strerror(ret)); + return ret; +} + + +status_t +AudioAdapter::InitCheck() const +{ + status_t error = AudioReader::InitCheck(); + if (error == B_OK && !fFinalConverter) + error = B_NO_INIT; + if (error == B_OK && fFinalConverter) + error = fFinalConverter->InitCheck(); + return error; +} + + +AudioReader* +AudioAdapter::Source() const +{ + return fSource; +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioAdapter.h b/src/apps/mediaplayer/media_node_framework/audio/AudioAdapter.h new file mode 100644 index 0000000000..a33489e20c --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioAdapter.h @@ -0,0 +1,45 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ + +/*! This AudioReader slaves an AudioConverter and an AudioResampler + to convert the source data to a given format. + At this time the number of channels cannot be changed and the output + format byte order is set to the one of the host. + If input and output format are the same, the overhead is quit small. +*/ + +#ifndef AUDIO_ADAPTER_H +#define AUDIO_ADAPTER_H + +#include "AudioReader.h" + +class AudioChannelConverter; +class AudioFormatConverter; +class AudioResampler; + +class AudioAdapter : public AudioReader { +public: + AudioAdapter(AudioReader* source, + const media_format& format); + virtual ~AudioAdapter(); + + virtual status_t Read(void* buffer, int64 pos, int64 frames); + + virtual status_t InitCheck() const; + + AudioReader* Source() const; + +protected: + void _ConvertChannels(void* buffer, + int64 frames) const; + + AudioReader* fSource; + AudioReader* fFinalConverter; + AudioFormatConverter* fFormatConverter; + AudioChannelConverter* fChannelConverter; + AudioResampler* fResampler; +}; + +#endif // AUDIO_ADAPTER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioChannelConverter.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioChannelConverter.cpp new file mode 100644 index 0000000000..0ece63227e --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioChannelConverter.cpp @@ -0,0 +1,140 @@ +/* + * Copyright © 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#include "AudioChannelConverter.h" + +#include +#include +#include +#include + +using std::nothrow; + +//#define TRACE_AUDIO_CONVERTER +#ifdef TRACE_AUDIO_CONVERTER +# define TRACE(x...) printf(x) +#else +# define TRACE(x...) +#endif + + +AudioChannelConverter::AudioChannelConverter(AudioReader* source, + const media_format& format) + : AudioReader(format), + fSource(source) +{ + // TODO: check the format and make sure everything matches + // except for channel count +} + + +AudioChannelConverter::~AudioChannelConverter() +{ +} + + +template +static void +convert(Type* inBuffer, Type* outBuffer, int32 inChannels, int32 outChannels, + int32 frames) +{ + // TODO: more conversions! + switch (inChannels) { + case 1: + switch (outChannels) { + case 2: + for (int32 i = 0; i < frames; i++) { + *outBuffer++ = *inBuffer; + *outBuffer++ = *inBuffer++; + } + break; + } + break; + case 2: + switch (outChannels) { + case 1: + for (int32 i = 0; i < frames; i++) { + *outBuffer++ + = (Type)((BigType)inBuffer[0] + inBuffer[1]) / 2; + inBuffer += 2; + } + break; + } + break; + } +} + + +status_t +AudioChannelConverter::Read(void* outBuffer, int64 pos, int64 frames) +{ + TRACE("AudioChannelConverter::Read(%p, %Ld, %Ld)\n", outBuffer, pos, frames); + status_t error = InitCheck(); + if (error != B_OK) + return error; + pos += fOutOffset; + + int32 inChannels = fSource->Format().u.raw_audio.channel_count; + int32 outChannels = fFormat.u.raw_audio.channel_count; + TRACE(" convert %ld -> %ld channels\n", inChannels, outChannels); + + int32 inSampleSize = fSource->Format().u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 inFrameSize = inSampleSize * inChannels; + uint8* inBuffer = new (nothrow) uint8[inFrameSize * frames]; + + TRACE(" fSource->Read()\n"); + status_t ret = fSource->Read(inBuffer, pos, frames); + if (ret != B_OK) { + delete[] inBuffer; + return ret; + } + + // We know that both formats are the same except for channel count + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + convert((float*)inBuffer, (float*)outBuffer, + inChannels, outChannels, frames); + break; + case media_raw_audio_format::B_AUDIO_INT: + convert((int32*)inBuffer, (int32*)outBuffer, + inChannels, outChannels, frames); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + convert((int16*)inBuffer, (int16*)outBuffer, + inChannels, outChannels, frames); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + convert((uint8*)inBuffer, (uint8*)outBuffer, + inChannels, outChannels, frames); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + convert((int8*)inBuffer, (int8*)outBuffer, + inChannels, outChannels, frames); + break; + } + + delete[] inBuffer; + + TRACE("AudioChannelConverter::Read() done: %s\n", strerror(ret)); + return ret; +} + + +status_t +AudioChannelConverter::InitCheck() const +{ + status_t error = AudioReader::InitCheck(); + if (error == B_OK && !fSource) + error = B_NO_INIT; + return error; +} + + +AudioReader* +AudioChannelConverter::Source() const +{ + return fSource; +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioChannelConverter.h b/src/apps/mediaplayer/media_node_framework/audio/AudioChannelConverter.h new file mode 100644 index 0000000000..029ec9e562 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioChannelConverter.h @@ -0,0 +1,32 @@ +/* + * Copyright © 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ + +/*! This AudioReader just converts the source channel count + into another one, e.g. 1 -> 2. Frame rate and sample format + remain unchanged. +*/ + +#ifndef AUDIO_CHANNEL_CONVERTER_H +#define AUDIO_CHANNEL_CONVERTER_H + +#include "AudioReader.h" + +class AudioChannelConverter : public AudioReader { +public: + AudioChannelConverter(AudioReader* source, + const media_format& format); + virtual ~AudioChannelConverter(); + + virtual status_t Read(void* buffer, int64 pos, int64 frames); + + virtual status_t InitCheck() const; + + AudioReader* Source() const; + +protected: + AudioReader* fSource; +}; + +#endif // AUDIO_CHANNEL_CONVERTER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioFormatConverter.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioFormatConverter.cpp new file mode 100644 index 0000000000..d959c4805e --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioFormatConverter.cpp @@ -0,0 +1,381 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * Copyright © 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ + +#include "AudioFormatConverter.h" + +#include + +#include +#include + + +//#define TRACE_AUDIO_CONVERTER +#ifdef TRACE_AUDIO_CONVERTER +# define TRACE(x...) printf(x) +#else +# define TRACE(x...) +#endif + + +AudioFormatConverter::AudioFormatConverter(AudioReader* source, uint32 format, + uint32 byte_order) + : AudioReader(), + fSource(NULL) +{ + uint32 hostByteOrder + = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + if (source && source->Format().type == B_MEDIA_RAW_AUDIO + && source->Format().u.raw_audio.byte_order == hostByteOrder) { + fFormat = source->Format(); + fFormat.u.raw_audio.format = format; + fFormat.u.raw_audio.byte_order = byte_order; + int32 inSampleSize = source->Format().u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 outSampleSize = fFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + if (inSampleSize != outSampleSize) { + fFormat.u.raw_audio.buffer_size + = source->Format().u.raw_audio.buffer_size * outSampleSize + / inSampleSize; + } + } else + source = NULL; + fSource = source; +} + + +AudioFormatConverter::~AudioFormatConverter() +{ +} + + +struct ReadFloat { + inline int operator()(const void* buffer) const { + // 0 == mid, -1.0 == bottom, 1.0 == top + float b = *(float*)buffer; + if (b < -1.0f) + b = -1.0f; + else if (b > 1.0f) + b = 1.0f; + return (int)((double)b * (double)0x7fffffff); + } +}; + +struct ReadInt { + inline int operator()(const void* buffer) const { + // 0 == mid, 0x80000001 == bottom, 0x7fffffff == top + short b = *(int*)buffer; + if (b == 0x80000000) + b++; + return b; + } +}; + +struct ReadShort { + inline int operator()(const void* buffer) const { + // 0 == mid, -32767 == bottom, +32767 + short b = *(short*)buffer; + if (b == -32768) + b++; + return int(int64(b) * 0x7fffffff / 32767); + } +}; + +struct ReadUChar { + inline int operator()(const void* buffer) const { + // 128 == mid, 1 == bottom, 255 == top + uchar b = *(uchar*)buffer; + if (b == 0) + b++; + return int((int64(b) - 0x80) * 0x7fffffff / 127); + } +}; + +struct ReadChar { + inline int operator()(const void* buffer) const { + // 0 == mid, -127 == bottom, +127 == top + char b = *(char*)buffer; + if (b == 0) + b++; + return int(int64(b) * 0x7fffffff / 127); + } +}; + +struct WriteFloat { + inline void operator()(void* buffer, int value) const { + *(float*)buffer = (double)value / (double)0x7fffffff; + } + +}; + +struct WriteInt { + inline void operator()(void* buffer, int value) const { + *(int*)buffer = value; + } +}; + +struct WriteShort { + inline void operator()(void* buffer, int value) const { + *(short*)buffer = (short)(value / (int)0x10000); + } +}; + +struct WriteUChar { + inline void operator()(void* buffer, int value) const { + *(uchar*)buffer = (uchar)(value / (int)0x1000000 + 128); + } +}; + +struct WriteChar { + inline void operator()(void* buffer, int value) const { + *(char*)buffer = (char)(value / (int)0x1000000); + } +}; + + +template +static void +convert(const ReadT& read, const WriteT& write, + const char* inBuffer, char* outBuffer, int32 frames, + int32 inSampleSize, int32 outSampleSize, int32 channelCount) +{ + for (int32 i = 0; i < frames; i++) { + for (int32 c = 0; c < channelCount; c++) { + write(outBuffer, read(inBuffer)); + inBuffer += inSampleSize; + outBuffer += outSampleSize; + } + } +} + + +static +void +swap_sample_byte_order(void* buffer, uint32 format, size_t length) +{ + type_code type = B_ANY_TYPE; + switch (format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + type = B_FLOAT_TYPE; + break; + case media_raw_audio_format::B_AUDIO_INT: + type = B_INT32_TYPE; + break; + case media_raw_audio_format::B_AUDIO_SHORT: + type = B_INT16_TYPE; + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + break; + case media_raw_audio_format::B_AUDIO_CHAR: + break; + } + if (type != B_ANY_TYPE) + swap_data(type, buffer, length, B_SWAP_ALWAYS); +} + + +status_t +AudioFormatConverter::Read(void* buffer, int64 pos, int64 frames) +{ + TRACE("AudioFormatConverter::Read(%p, %Ld, %Ld)\n", buffer, pos, frames); + status_t error = InitCheck(); + if (error != B_OK) { + TRACE("AudioFormatConverter::Read() done 1\n"); + return error; + } + pos += fOutOffset; + + if (fFormat.u.raw_audio.format == fSource->Format().u.raw_audio.format + && fFormat.u.raw_audio.byte_order + == fSource->Format().u.raw_audio.byte_order) { + TRACE("AudioFormatConverter::Read() done 2\n"); + return fSource->Read(buffer, pos, frames); + } + + + int32 inSampleSize = fSource->Format().u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 outSampleSize = fFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 channelCount = fFormat.u.raw_audio.channel_count; + int32 inFrameSize = inSampleSize * channelCount; + int32 outFrameSize = outSampleSize * channelCount; + char* reformatBuffer = NULL; + char* inBuffer = (char*)buffer; + + #ifdef TRACE_AUDIO_CONVERTER + char formatString[256]; + string_for_format(fSource->Format(), formatString, 256); + TRACE(" source format: %s\n", formatString); + TRACE(" in format : format: %lx, sample size: %ld, channels: %ld, " + "byte order: %lu\n", fSource->Format().u.raw_audio.format, + inSampleSize, channelCount, + fSource->Format().u.raw_audio.byte_order); + TRACE(" out format: format: %lx, sample size: %ld, channels: %ld, " + "byte order: %lu\n", fFormat.u.raw_audio.format, outSampleSize, + channelCount, fFormat.u.raw_audio.byte_order); + #endif // TRACE_AUDIO_CONVERTER + + if (inSampleSize != outSampleSize) { + reformatBuffer = new char[frames * inFrameSize]; + inBuffer = reformatBuffer; + } + error = fSource->Read(inBuffer, pos, frames); + // convert samples to host endianess + uint32 hostByteOrder + = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + if (fSource->Format().u.raw_audio.byte_order != hostByteOrder) { + swap_sample_byte_order(inBuffer, fSource->Format().u.raw_audio.format, + frames * inFrameSize); + } + // convert the sample type + switch (fSource->Format().u.raw_audio.format) { + // float + case media_raw_audio_format::B_AUDIO_FLOAT: + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + break; + case media_raw_audio_format::B_AUDIO_INT: + convert(ReadFloat(), WriteInt(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + convert(ReadFloat(), WriteShort(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + convert(ReadFloat(), WriteUChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + convert(ReadFloat(), WriteChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + } + break; + // int + case media_raw_audio_format::B_AUDIO_INT: + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + convert(ReadInt(), WriteFloat(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_INT: + break; + case media_raw_audio_format::B_AUDIO_SHORT: + convert(ReadInt(), WriteShort(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + convert(ReadInt(), WriteUChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + convert(ReadInt(), WriteChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + } + break; + // short + case media_raw_audio_format::B_AUDIO_SHORT: + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + convert(ReadShort(), WriteFloat(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_INT: + convert(ReadShort(), WriteInt(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + convert(ReadShort(), WriteUChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + convert(ReadShort(), WriteChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + } + break; + // uchar + case media_raw_audio_format::B_AUDIO_UCHAR: + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + convert(ReadUChar(), WriteFloat(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_INT: + convert(ReadUChar(), WriteInt(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + convert(ReadUChar(), WriteShort(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + break; + case media_raw_audio_format::B_AUDIO_CHAR: + convert(ReadUChar(), WriteChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + } + break; + // char + case media_raw_audio_format::B_AUDIO_CHAR: + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + convert(ReadChar(), WriteFloat(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_INT: + convert(ReadChar(), WriteInt(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + convert(ReadChar(), WriteShort(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + convert(ReadChar(), WriteUChar(), inBuffer, (char*)buffer, + frames, inSampleSize, outSampleSize, channelCount); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + break; + } + break; + } + // convert samples to output endianess + if (fFormat.u.raw_audio.byte_order != hostByteOrder) { + swap_sample_byte_order(buffer, fFormat.u.raw_audio.format, + frames * outFrameSize); + } + + delete[] reformatBuffer; + TRACE("AudioFormatConverter::Read() done\n"); + return B_OK; +} + + +status_t +AudioFormatConverter::InitCheck() const +{ + status_t error = AudioReader::InitCheck(); + if (error == B_OK && !fSource) + error = B_NO_INIT; + if (error == B_OK) + error = fSource->InitCheck(); + return error; +} + + +AudioReader* +AudioFormatConverter::Source() const +{ + return fSource; +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioFormatConverter.h b/src/apps/mediaplayer/media_node_framework/audio/AudioFormatConverter.h new file mode 100644 index 0000000000..0894fc57f2 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioFormatConverter.h @@ -0,0 +1,33 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * Copyright © 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ + +/*! This AudioReader just converts the source sample format (and byte order) + into another one, e.g. LE short -> BE float. Frame rate and channel + count remain unchanged. +*/ + +#ifndef AUDIO_FORMAT_CONVERTER_H +#define AUDIO_FORMAT_CONVERTER_H + +#include "AudioReader.h" + +class AudioFormatConverter : public AudioReader { +public: + AudioFormatConverter(AudioReader* source, + uint32 format, uint32 byte_order); + virtual ~AudioFormatConverter(); + + virtual status_t Read(void* buffer, int64 pos, int64 frames); + + virtual status_t InitCheck() const; + + AudioReader* Source() const; + +protected: + AudioReader* fSource; +}; + +#endif // AUDIO_FORMAT_CONVERTER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp new file mode 100644 index 0000000000..f203fd632b --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.cpp @@ -0,0 +1,742 @@ +/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "AudioProducer.h" + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include "AudioSupplier.h" + +#define DEBUG_TO_FILE 0 + +#if DEBUG_TO_FILE +# include +# include +# include +# include +#endif // DEBUG_TO_FILE + + +// debugging +//#define TRACE_AUDIO_PRODUCER +#ifdef TRACE_AUDIO_PRODUCER +# define TRACE(x...) printf(x) +# define ERROR(x...) fprintf(stderr, x) +#else +# define TRACE(x...) +# define ERROR(x...) fprintf(stderr, x) +#endif + + +#if DEBUG_TO_FILE +static BMediaFile* +init_media_file(media_format format, BMediaTrack** _track) +{ + static BMediaFile* file = NULL; + static BMediaTrack* track = NULL; + if (file == NULL) { + entry_ref ref; + get_ref_for_path("/boot/home/Desktop/test.wav", &ref); + + media_file_format fileFormat; + int32 cookie = 0; + while (get_next_file_format(&cookie, &fileFormat) == B_OK) { + if (strcmp(fileFormat.short_name, "wav") == 0) { + break; + } + } + file = new BMediaFile(&ref, &fileFormat); + + media_codec_info info; + cookie = 0; + while (get_next_encoder(&cookie, &info) == B_OK) { + if (strcmp(info.short_name, "raw-audio") == 0) + break; + } + + track = file->CreateTrack(&format, &info); + if (!track) + printf("failed to create track\n"); + + file->CommitHeader(); + } + *_track = track; + return track != NULL ? file : NULL; +} +#endif // DEBUG_TO_FILE + + + +// constructor +AudioProducer::AudioProducer(const char* name, AudioSupplier* supplier, + bool lowLatency) + : BMediaNode(name), + BBufferProducer(B_MEDIA_RAW_AUDIO), + BMediaEventLooper(), + + fBufferGroup(NULL), + fLatency(0), + fInternalLatency(0), + fLowLatency(lowLatency), + fOutputEnabled(true), + fFramesSent(0), + fStartTime(0), + fSupplier(supplier), + fRunning(false) +{ + TRACE("%p->AudioProducer::AudioProducer(%s, %p, %d)\n", this, name, + supplier, lowLatency); + + // initialize our preferred format object + fPreferredFormat.type = B_MEDIA_RAW_AUDIO; + fPreferredFormat.u.raw_audio.format + = media_raw_audio_format::B_AUDIO_FLOAT; +// = media_raw_audio_format::B_AUDIO_SHORT; + fPreferredFormat.u.raw_audio.channel_count = 2; + fPreferredFormat.u.raw_audio.frame_rate = 44100.0; + fPreferredFormat.u.raw_audio.byte_order + = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + + // NOTE: the (buffer_size * 1000000) needs to be dividable by + // fPreferredFormat.u.raw_audio.frame_rate! + fPreferredFormat.u.raw_audio.buffer_size = 441 * 2 + * (fPreferredFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK); + + if (!fLowLatency) + fPreferredFormat.u.raw_audio.buffer_size *= 6; + + // we're not connected yet + fOutput.destination = media_destination::null; + fOutput.format = fPreferredFormat; + // init the audio supplier + if (fSupplier) { + fSupplier->SetAudioProducer(this); + } +} + + +AudioProducer::~AudioProducer() +{ + TRACE("%p->AudioProducer::~AudioProducer()\n", this); + +#if DEBUG_TO_FILE + BMediaTrack* track; + if (BMediaFile* file = init_media_file(fPreferredFormat, &track)) { + printf("deleting file...\n"); + track->Flush(); + file->ReleaseTrack(track); + file->CloseFile(); + delete file; + } +#endif // DEBUG_TO_FILE + + // Stop the BMediaEventLooper thread + Quit(); + TRACE("AudioProducer::~AudioProducer() done\n"); +} + + +BMediaAddOn* +AudioProducer::AddOn(int32* internalId) const +{ + return NULL; +} + + +status_t +AudioProducer::FormatSuggestionRequested(media_type type, int32 quality, + media_format* _format) +{ + TRACE("%p->AudioProducer::FormatSuggestionRequested()\n", this); + + if (!_format) + return B_BAD_VALUE; + + // this is the format we'll be returning (our preferred format) + *_format = fPreferredFormat; + + // a wildcard type is okay; we can specialize it + if (type == B_MEDIA_UNKNOWN_TYPE) + type = B_MEDIA_RAW_AUDIO; + + // we only support raw audio + if (type != B_MEDIA_RAW_AUDIO) + return B_MEDIA_BAD_FORMAT; + + return B_OK; +} + +status_t +AudioProducer::FormatProposal(const media_source& output, media_format* format) +{ + TRACE("%p->AudioProducer::FormatProposal()\n", this); + + // is this a proposal for our one output? + if (output != fOutput.source) { + TRACE(" -> B_MEDIA_BAD_SOURCE\n"); + return B_MEDIA_BAD_SOURCE; + } + + // TODO: we might want to support different audio formats + // we only support floating-point raw audio, so we always return that, but + // we supply an error code depending on whether we found the proposal + // acceptable. + media_type requestedType = format->type; + *format = fPreferredFormat; + + // raw audio or wildcard type, either is okay by us + if (requestedType != B_MEDIA_UNKNOWN_TYPE + && requestedType != B_MEDIA_RAW_AUDIO) { + TRACE(" -> B_MEDIA_BAD_FORMAT\n"); + return B_MEDIA_BAD_FORMAT; + } + + return B_OK; +} + + +status_t +AudioProducer::FormatChangeRequested(const media_source& source, + const media_destination& destination, media_format* ioFormat, + int32* _deprecated_) +{ + TRACE("%p->AudioProducer::FormatChangeRequested()\n", this); + + if (destination != fOutput.destination) { + TRACE(" -> B_MEDIA_BAD_DESTINATION\n"); + return B_MEDIA_BAD_DESTINATION; + } + + if (source != fOutput.source) { + TRACE(" -> B_MEDIA_BAD_SOURCE\n"); + return B_MEDIA_BAD_SOURCE; + } + + fOutput.format = *ioFormat; + + // notify our audio supplier of the format change + if (fSupplier) + fSupplier->SetFormat(fOutput.format); + + return _AllocateBuffers(ioFormat); +} + + +status_t +AudioProducer::GetNextOutput(int32* cookie, media_output* _output) +{ + TRACE("%p->AudioProducer::GetNextOutput(%ld)\n", this, *cookie); + + // we have only a single output; if we supported multiple outputs, we'd + // iterate over whatever data structure we were using to keep track of + // them. + if (0 == *cookie) { + *_output = fOutput; + *cookie += 1; + return B_OK; + } + + return B_BAD_INDEX; +} + + +status_t +AudioProducer::DisposeOutputCookie(int32 cookie) +{ + // do nothing because we don't use the cookie for anything special + return B_OK; +} + + +status_t +AudioProducer::SetBufferGroup(const media_source& forSource, + BBufferGroup* newGroup) +{ + TRACE("%p->AudioProducer::SetBufferGroup()\n", this); + + if (forSource != fOutput.source) + return B_MEDIA_BAD_SOURCE; + + if (newGroup == fBufferGroup) + return B_OK; + + if (fUsingOurBuffers && fBufferGroup) + delete fBufferGroup; // waits for all buffers to recycle + + if (newGroup != NULL) { + // we were given a valid group; just use that one from now on + fBufferGroup = newGroup; + fUsingOurBuffers = false; + } else { + // we were passed a NULL group pointer; that means we construct + // our own buffer group to use from now on + size_t size = fOutput.format.u.raw_audio.buffer_size; + int32 count = int32(fLatency / BufferDuration() + 1 + 1); + fBufferGroup = new BBufferGroup(size, count); + fUsingOurBuffers = true; + } + + return B_OK; +} + + +status_t +AudioProducer::GetLatency(bigtime_t* _latency) +{ + TRACE("%p->AudioProducer::GetLatency()\n", this); + + // report our *total* latency: internal plus downstream plus scheduling + *_latency = EventLatency() + SchedulingLatency(); + return B_OK; +} + + +status_t +AudioProducer::PrepareToConnect(const media_source& what, + const media_destination& where, media_format* format, + media_source* _source, char* _name) +{ + TRACE("%p->AudioProducer::PrepareToConnect()\n", this); + + // trying to connect something that isn't our source? + if (what != fOutput.source) { + TRACE(" -> B_MEDIA_BAD_SOURCE\n"); + return B_MEDIA_BAD_SOURCE; + } + + // are we already connected? + if (fOutput.destination != media_destination::null) { + TRACE(" -> B_MEDIA_ALREADY_CONNECTED\n"); + return B_MEDIA_ALREADY_CONNECTED; + } + + // the format may not yet be fully specialized (the consumer might have + // passed back some wildcards). Finish specializing it now, and return an + // error if we don't support the requested format. + if (format->type != B_MEDIA_RAW_AUDIO) { + TRACE(" -> B_MEDIA_BAD_FORMAT\n"); + return B_MEDIA_BAD_FORMAT; +// TODO: we might want to support different audio formats + } else if (format->u.raw_audio.format + != fPreferredFormat.u.raw_audio.format) { + TRACE(" -> B_MEDIA_BAD_FORMAT\n"); + return B_MEDIA_BAD_FORMAT; + } + + // !!! validate all other fields except for buffer_size here, because the + // consumer might have supplied different values from AcceptFormat()? + + // check the buffer size, which may still be wildcarded + if (format->u.raw_audio.buffer_size + == media_raw_audio_format::wildcard.buffer_size) { + // pick something comfortable to suggest + // NOTE: the (buffer_size * 1000000) needs to be dividable by + // fPreferredFormat.u.raw_audio.frame_rate! + // TODO: this needs to depend on the other parameters + // (but it doesn't matter sincer the AudioProducer is not + // currently used like that) + TRACE(" -> adjusting buffer size, it was wildcard\n"); + format->u.raw_audio.buffer_size = 441 * 2 * sizeof(float); + } + + // Now reserve the connection, and return information about it + fOutput.destination = where; + fOutput.format = *format; + *_source = fOutput.source; + strncpy(_name, fOutput.name, B_MEDIA_NAME_LENGTH); + TRACE(" -> B_OK\n"); + return B_OK; +} + + +static bigtime_t +estimate_internal_latency(const media_format& format) +{ + bigtime_t startTime = system_time(); + // calculate the number of samples per buffer + int32 sampleSize = format.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 sampleCount = format.u.raw_audio.buffer_size / sampleSize; + // alloc float buffers of this size + const int bufferCount = 10; // number of input buffers + float* buffers[bufferCount + 1]; + for (int32 i = 0; i < bufferCount + 1; i++) + buffers[i] = new float[sampleCount]; + float* outBuffer = buffers[bufferCount]; + // fill all buffers save the last one with arbitrary data and merge them + // into the last one + for (int32 i = 0; i < bufferCount; i++) { + for (int32 k = 0; k < sampleCount; k++) { + buffers[i][k] = ((float)i * (float)k) + / float(bufferCount * sampleCount); + } + } + for (int32 k = 0; k < sampleCount; k++) { + outBuffer[k] = 0; + for (int32 i = 0; i < bufferCount; i++) + outBuffer[k] += buffers[i][k]; + outBuffer[k] /= bufferCount; + } + // cleanup + for (int32 i = 0; i < bufferCount + 1; i++) + delete[] buffers[i]; + return system_time() - startTime; +} + + +void +AudioProducer::Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, + char* _name) +{ + TRACE("AudioProducer::Connect(%s)\n", strerror(error)); + + // If something earlier failed, Connect() might still be called, but with + // a non-zero error code. When that happens we simply unreserve the + // connection and do nothing else. + if (error != B_OK) { + fOutput.destination = media_destination::null; + fOutput.format = fPreferredFormat; + return; + } + + // Okay, the connection has been confirmed. Record the destination and + // format that we agreed on, and report our connection name again. + fOutput.destination = destination; + fOutput.format = format; + strncpy(_name, fOutput.name, B_MEDIA_NAME_LENGTH); + + // tell our audio supplier about the format + if (fSupplier) { + TRACE("AudioProducer::Connect() fSupplier->SetFormat()\n"); + fSupplier->SetFormat(fOutput.format); + } + + TRACE("AudioProducer::Connect() FindLatencyFor()\n"); + + // Now that we're connected, we can determine our downstream latency. + // Do so, then make sure we get our events early enough. + media_node_id id; + FindLatencyFor(fOutput.destination, &fLatency, &id); + + // Use a dry run to see how long it takes me to fill a buffer of data + size_t sampleSize = fOutput.format.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + size_t samplesPerBuffer + = fOutput.format.u.raw_audio.buffer_size / sampleSize; + fInternalLatency = estimate_internal_latency(fOutput.format); + if (!fLowLatency) + fInternalLatency *= 32; + SetEventLatency(fLatency + fInternalLatency); + + // reset our buffer duration, etc. to avoid later calculations + bigtime_t duration = bigtime_t(1000000) + * samplesPerBuffer / bigtime_t(fOutput.format.u.raw_audio.frame_rate + * fOutput.format.u.raw_audio.channel_count); + TRACE("AudioProducer::Connect() SetBufferDuration(%lld)\n", duration); + SetBufferDuration(duration); + + TRACE("AudioProducer::Connect() _AllocateBuffers()\n"); + + // Set up the buffer group for our connection, as long as nobody handed + // us a buffer group (via SetBufferGroup()) prior to this. That can + // happen, for example, if the consumer calls SetOutputBuffersFor() on + // us from within its Connected() method. + if (!fBufferGroup) + _AllocateBuffers(&fOutput.format); + + TRACE("AudioProducer::Connect() done\n"); +} + + +void +AudioProducer::Disconnect(const media_source& what, + const media_destination& where) +{ + TRACE("%p->AudioProducer::Disconnect()\n", this); + + // Make sure that our connection is the one being disconnected + if ((where == fOutput.destination) && (what == fOutput.source)) { + fOutput.destination = media_destination::null; + fOutput.format = fPreferredFormat; + TRACE("AudioProducer: deleting buffer group...\n"); + // Always delete the buffer group, even if it is not ours. + // (See BeBook::SetBufferGroup()). + delete fBufferGroup; + TRACE("AudioProducer: buffer group deleted\n"); + fBufferGroup = NULL; + } + + TRACE("%p->AudioProducer::Disconnect() done\n", this); +} + + +void +AudioProducer::LateNoticeReceived(const media_source& what, bigtime_t howMuch, + bigtime_t performanceTime) +{ + ERROR("%p->AudioProducer::LateNoticeReceived(%lld, %lld)\n", this, howMuch, + performanceTime); + // If we're late, we need to catch up. Respond in a manner appropriate + // to our current run mode. + if (what == fOutput.source) { + if (RunMode() == B_RECORDING) { + // ... + } else if (RunMode() == B_INCREASE_LATENCY) { + fInternalLatency += howMuch; + SetEventLatency(fLatency + fInternalLatency); + } else { + size_t sampleSize + = fOutput.format.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + size_t nSamples + = fOutput.format.u.raw_audio.buffer_size / sampleSize; + fFramesSent += nSamples; + } + } +} + + +void +AudioProducer::EnableOutput(const media_source& what, bool enabled, + int32* _deprecated_) +{ + TRACE("%p->AudioProducer::EnableOutput(%d)\n", this, enabled); + + if (what == fOutput.source) + fOutputEnabled = enabled; +} + + +status_t +AudioProducer::SetPlayRate(int32 numer, int32 denom) +{ + return B_ERROR; +} + + +status_t +AudioProducer::HandleMessage(int32 message, const void *data, size_t size) +{ + TRACE("%p->AudioProducer::HandleMessage()\n", this); + return B_ERROR; +} + + +void +AudioProducer::AdditionalBufferRequested(const media_source& source, + media_buffer_id prevBuffer, bigtime_t prevTime, + const media_seek_tag *prevTag) +{ + TRACE("%p->AudioProducer::AdditionalBufferRequested()\n", this); +} + + +void +AudioProducer::LatencyChanged(const media_source& source, + const media_destination& destination, bigtime_t newLatency, uint32 flags) +{ + TRACE("%p->AudioProducer::LatencyChanged(%lld)\n", this, newLatency); + + if ((source == fOutput.source) && (destination == fOutput.destination)) { + fLatency = newLatency; + SetEventLatency(fLatency + fInternalLatency); + } +} + + +void +AudioProducer::NodeRegistered() +{ + TRACE("%p->AudioProducer::NodeRegistered()\n", this); + + // Start the BMediaEventLooper thread + SetPriority(B_REAL_TIME_PRIORITY); + Run(); + + // set up as much information about our output as we can + fOutput.source.port = ControlPort(); + fOutput.source.id = 0; + fOutput.node = Node(); + ::strcpy(fOutput.name, "MediaPlayer Sound Output"); +} + + +void +AudioProducer::SetRunMode(run_mode mode) +{ + TRACE("%p->AudioProducer::SetRunMode()\n", this); + + if (B_OFFLINE == mode) + ReportError(B_NODE_FAILED_SET_RUN_MODE); + else + BBufferProducer::SetRunMode(mode); +} + + +void +AudioProducer::HandleEvent(const media_timed_event* event, bigtime_t lateness, + bool realTimeEvent) +{ + TRACE("%p->AudioProducer::HandleEvent()\n", this); + + switch (event->type) { + case BTimedEventQueue::B_START: + TRACE("AudioProducer::HandleEvent(B_START)\n"); + if (RunState() != B_STARTED) { + fFramesSent = 0; + fStartTime = event->event_time; + media_timed_event firstBufferEvent(fStartTime, + BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(firstBufferEvent); + } + TRACE("AudioProducer::HandleEvent(B_START) done\n"); + break; + + case BTimedEventQueue::B_STOP: + TRACE("AudioProducer::HandleEvent(B_STOP)\n"); + EventQueue()->FlushEvents(0, BTimedEventQueue::B_ALWAYS, true, + BTimedEventQueue::B_HANDLE_BUFFER); + TRACE("AudioProducer::HandleEvent(B_STOP) done\n"); + break; + + case BTimedEventQueue::B_HANDLE_BUFFER: { + TRACE("AudioProducer::HandleEvent(B_HANDLE_BUFFER)\n"); + if ((RunState() == BMediaEventLooper::B_STARTED) + && (fOutput.destination != media_destination::null)) { + BBuffer* buffer = _FillNextBuffer(event->event_time); + if (buffer) { + status_t err = B_ERROR; + if (fOutputEnabled) + err = SendBuffer(buffer, fOutput.destination); + if (err) + buffer->Recycle(); + } + size_t sampleSize + = fOutput.format.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + + size_t nFrames = fOutput.format.u.raw_audio.buffer_size + / (sampleSize + * fOutput.format.u.raw_audio.channel_count); + fFramesSent += nFrames; + + bigtime_t nextEvent = fStartTime + + bigtime_t(double(fFramesSent) * 1000000.0 + / double(fOutput.format.u.raw_audio.frame_rate)); + media_timed_event nextBufferEvent(nextEvent, + BTimedEventQueue::B_HANDLE_BUFFER); + EventQueue()->AddEvent(nextBufferEvent); + } + TRACE("AudioProducer::HandleEvent(B_HANDLE_BUFFER) done\n"); + break; + } + default: + break; + } +} + + +void +AudioProducer::SetRunning(bool running) +{ + TRACE("%p->AudioProducer::SetRunning(%d)\n", this, running); + + fRunning = running; +} + + +// #pragma mark - + + +status_t +AudioProducer::_AllocateBuffers(media_format* format) +{ + TRACE("%p->AudioProducer::_AllocateBuffers()\n", this); + + if (fBufferGroup && fUsingOurBuffers) { + delete fBufferGroup; + fBufferGroup = NULL; + } + size_t size = format->u.raw_audio.buffer_size; + int32 bufferDuration = BufferDuration(); + int32 count = 0; + if (bufferDuration > 0) { + count = (int32)((fLatency + fInternalLatency) + / bufferDuration + 2); + } + + fBufferGroup = new BBufferGroup(size, count); + fUsingOurBuffers = true; + return fBufferGroup->InitCheck(); +} + + +BBuffer* +AudioProducer::_FillNextBuffer(bigtime_t eventTime) +{ + BBuffer* buffer = fBufferGroup->RequestBuffer( + fOutput.format.u.raw_audio.buffer_size, BufferDuration()); + + if (!buffer) { + TRACE("AudioProducer::_FillNextBuffer() - no buffer\n"); + return NULL; + } + + size_t sampleSize = fOutput.format.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + size_t numSamples = fOutput.format.u.raw_audio.buffer_size / sampleSize; + // number of sample in the buffer + + // fill in the buffer header + media_header* hdr = buffer->Header(); + hdr->type = B_MEDIA_RAW_AUDIO; + hdr->time_source = TimeSource()->ID(); + buffer->SetSizeUsed(fOutput.format.u.raw_audio.buffer_size); + + bigtime_t performanceTime = bigtime_t(double(fFramesSent) + * 1000000.0 / double(fOutput.format.u.raw_audio.frame_rate)); + + // fill in data from audio supplier + size_t frameCount = numSamples / fOutput.format.u.raw_audio.channel_count; + bigtime_t startTime = performanceTime; + bigtime_t endTime = bigtime_t(double(fFramesSent + frameCount) + * 1000000.0 / double(fOutput.format.u.raw_audio.frame_rate)); + + if (!fSupplier || fSupplier->InitCheck() != B_OK + || fSupplier->GetFrames(buffer->Data(), frameCount, startTime, + endTime) != B_OK) { + TRACE("AudioProducer::_FillNextBuffer() - error -> silence\n"); + memset(buffer->Data(), 0, buffer->SizeUsed()); + } + + // stamp buffer + if (RunMode() == B_RECORDING) { + hdr->start_time = eventTime; + } else { + hdr->start_time = fStartTime + performanceTime; + } + +#if DEBUG_TO_FILE + BMediaTrack* track; + if (BMediaFile* file = init_media_file(fOutput.format, &track)) { + track->WriteFrames(buffer->Data(), frameCount); + } +#endif // DEBUG_TO_FILE + + return buffer; +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h new file mode 100644 index 0000000000..16528a80b2 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioProducer.h @@ -0,0 +1,121 @@ +/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef AUDIO_PRODUCER_H +#define AUDIO_PRODUCER_H + +#include +//#include +#include + +class AudioSupplier; + +class AudioProducer : public BBufferProducer, public BMediaEventLooper { +public: + AudioProducer(const char* name, + AudioSupplier* supplier, + bool lowLatency = true); + virtual ~AudioProducer(); + + // BMediaNode interface + virtual BMediaAddOn* AddOn(int32 *internal_id) const; + + // BBufferProducer interface + virtual status_t FormatSuggestionRequested(media_type type, + int32 quality, media_format* _format); + + virtual status_t FormatProposal(const media_source& output, + media_format* format); + + virtual status_t FormatChangeRequested( + const media_source& source, + const media_destination& destination, + media_format* ioFormat, + int32* _deprecated_); + + virtual status_t GetNextOutput(int32* cookie, + media_output* _output); + + virtual status_t DisposeOutputCookie(int32 cookie); + + virtual status_t SetBufferGroup(const media_source& forSource, + BBufferGroup* group); + + virtual status_t GetLatency(bigtime_t* _latency); + + virtual status_t PrepareToConnect(const media_source& what, + const media_destination& where, + media_format* format, + media_source* outSource, char* outName); + + virtual void Connect(status_t error, + const media_source& source, + const media_destination& destination, + const media_format& format, + char* ioName); + + virtual void Disconnect(const media_source &what, + const media_destination& where); + + virtual void LateNoticeReceived(const media_source& what, + bigtime_t howMuch, + bigtime_t performanceTime); + + virtual void EnableOutput(const media_source& what, + bool enabled, int32* _deprecated_); + + virtual status_t SetPlayRate(int32 numer, int32 denom); + + virtual status_t HandleMessage(int32 message, + const void* data, size_t size); + + virtual void AdditionalBufferRequested( + const media_source& source, + media_buffer_id prevBuffer, + bigtime_t prevTime, + const media_seek_tag* prevTag); + // may be NULL + + virtual void LatencyChanged(const media_source& source, + const media_destination& destination, + bigtime_t newLatency, uint32 flags); + + // BMediaEventLooper interface + virtual void NodeRegistered(); + + virtual void SetRunMode(run_mode mode); + + virtual void HandleEvent(const media_timed_event* event, + bigtime_t lateness, + bool realTimeEvent = false); + + void SetRunning(bool running); + +private: + status_t _AllocateBuffers(media_format* format); + BBuffer* _FillNextBuffer(bigtime_t eventTime); + + void _FillSampleBuffer(float* data, + size_t numSamples); + + BBufferGroup* fBufferGroup; + bool fUsingOurBuffers; + bigtime_t fLatency; + bigtime_t fInternalLatency; + bool fLowLatency; + media_output fOutput; + bool fOutputEnabled; + media_format fPreferredFormat; + + uint64 fFramesSent; + bigtime_t fStartTime; + + AudioSupplier* fSupplier; + volatile bool fRunning; +}; + +#endif // AUDIO_PRODUCER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioReader.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioReader.cpp new file mode 100644 index 0000000000..a0e3b9089c --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioReader.cpp @@ -0,0 +1,151 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#include +#include + +#include "AudioReader.h" + + +AudioReader::AudioReader() + : fFormat(), + fOutOffset(0) +{ +} + + +AudioReader::AudioReader(const media_format& format) + : fFormat(format), + fOutOffset(0) +{ +} + + +AudioReader::~AudioReader() +{ +} + + +status_t +AudioReader::InitCheck() const +{ + return B_OK; +} + + +void +AudioReader::SetFormat(const media_format& format) +{ + fFormat = format; +} + + +const media_format& +AudioReader::Format() const +{ + return fFormat; +} + + +void +AudioReader::SetOutOffset(int64 offset) +{ + fOutOffset = offset; +} + + +int64 +AudioReader::OutOffset() const +{ + return fOutOffset; +} + + +int64 +AudioReader::FrameForTime(bigtime_t time) const +{ + double frameRate = fFormat.u.raw_audio.frame_rate; + return int64(double(time) * frameRate / 1000000.0); +} + + +bigtime_t +AudioReader::TimeForFrame(int64 frame) const +{ + double frameRate = fFormat.u.raw_audio.frame_rate; + return bigtime_t(double(frame) * 1000000.0 / frameRate); +} + + +//! helper function for ReadSilence() +template +inline void +fill_buffer(void* buffer, int32 count, sample_t value) +{ + sample_t* buf = (sample_t*)buffer; + sample_t* bufferEnd = buf + count; + for (; buf < bufferEnd; buf++) + *buf = value; +} + + +/*! Fills the supplied buffer with /frames/ frames of silence and returns a + pointer to the frames after the filled range. + /frames/ must be >= 0.*/ +void* +AudioReader::ReadSilence(void* buffer, int64 frames) const +{ + void* bufferEnd = SkipFrames(buffer, frames); + int32 sampleCount = frames * fFormat.u.raw_audio.channel_count; + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + fill_buffer(buffer, sampleCount, (float)0); + break; + case media_raw_audio_format::B_AUDIO_INT: + fill_buffer(buffer, sampleCount, (int)0); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + fill_buffer(buffer, sampleCount, (short)0); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + fill_buffer(buffer, sampleCount, (uchar)128); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + fill_buffer(buffer, sampleCount, (uchar)0); + break; + default: + memset(buffer, 0, (char*)bufferEnd - (char*)buffer); + break; + } + return bufferEnd; +} + + +//! Returns a buffer pointer offset by /frames/ frames. +void* +AudioReader::SkipFrames(void* buffer, int64 frames) const +{ + int32 sampleSize = fFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 frameSize = sampleSize * fFormat.u.raw_audio.channel_count; + return (char*)buffer + frames * frameSize; +} + + +void +AudioReader::ReverseFrames(void* buffer, int64 frames) const +{ + int32 sampleSize = fFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 frameSize = sampleSize * fFormat.u.raw_audio.channel_count; + char* front = (char*)buffer; + char* back = (char*)buffer + (frames - 1) * frameSize; + while (front < back) { + for (int32 i = 0; i < frameSize; i++) + swap(front[i], back[i]); + front += frameSize; + back -= frameSize; + } +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioReader.h b/src/apps/mediaplayer/media_node_framework/audio/AudioReader.h new file mode 100644 index 0000000000..413bb4494c --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioReader.h @@ -0,0 +1,40 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#ifndef AUDIO_READER_H +#define AUDIO_READER_H + +#include + +class AudioReader { +public: + AudioReader(); + AudioReader(const media_format& format); + virtual ~AudioReader(); + + virtual status_t InitCheck() const; + + void SetFormat(const media_format& format); + const media_format& Format() const; + + virtual status_t Read(void* buffer, int64 pos, int64 frames) = 0; + + void SetOutOffset(int64 offset); + int64 OutOffset() const; + + int64 FrameForTime(bigtime_t time) const; + bigtime_t TimeForFrame(int64 frame) const; + +protected: + void* ReadSilence(void* buffer, int64 frames) const; + void* SkipFrames(void* buffer, int64 frames) const; + void ReverseFrames(void* buffer, + int64 frames) const; + +protected: + media_format fFormat; + int64 fOutOffset; +}; + +#endif // AUDIO_READER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioResampler.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioResampler.cpp new file mode 100644 index 0000000000..b9025181f0 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioResampler.cpp @@ -0,0 +1,277 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#include "AudioResampler.h" + +#include +#include + +#include "SampleBuffer.h" + + +//#define TRACE_AUDIO_RESAMPLER +#ifdef TRACE_AUDIO_RESAMPLER +# define TRACE(x...) printf(x) +#else +# define TRACE(x...) +#endif + + +AudioResampler::AudioResampler() + : AudioReader(), + fSource(NULL), + fTimeScale(1.0), + fInOffset(0) +{ +} + + +AudioResampler::AudioResampler(AudioReader* source, float frameRate, + float timeScale) + : AudioReader(), + fSource(NULL), + fTimeScale(timeScale), + fInOffset(0) +{ + SetSource(source); + if (fSource) + fFormat.u.raw_audio.frame_rate = frameRate; +} + + +AudioResampler::~AudioResampler() +{ +} + + +//! Calculates the greatest common divider of /a/ and /b/. +template inline +T +gcd(T a, T b) +{ + while (b != 0) { + T r = a % b; + a = b; + b = r; + } + return a; +} + + +template +static void +resample_linear(void* _inBuffer, void* _outBuffer, uint32 channelCount, + double inFrameRate, double outFrameRate, int32 frames) +{ + typedef double sample_t; + Buffer inBuffer(_inBuffer); + Buffer outFrameBuf(_outBuffer); + for (sample_t outFrame = 0; outFrame < frames; outFrame++) { + // time of the out sample + sample_t outTime = outFrame / outFrameRate; + // first in frame + int64 inFrame = int64(outTime * inFrameRate); + // time of the first and the second in frame + sample_t inTime1 = (sample_t)inFrame / inFrameRate; + sample_t inTime2 = (sample_t)(inFrame + 1) / inFrameRate; + // differences between the out frame time and the in frame times + sample_t timeDiff1 = outTime - inTime1; + sample_t timeDiff2 = inTime2 - outTime; + sample_t timeDiff = timeDiff1 + timeDiff2; + // pointer to the first and second in frame + Buffer inFrameBuf1 = inBuffer + inFrame * channelCount; + Buffer inFrameBuf2 = inFrameBuf1 + channelCount; + for (uint32 c = 0; c < channelCount; + c++, inFrameBuf1++, inFrameBuf2++, outFrameBuf++) { + // sum weighted according to the distance to the respective other + // in frame + outFrameBuf.WriteSample((timeDiff2 * inFrameBuf1.ReadSample() + + timeDiff1 * inFrameBuf2.ReadSample()) / timeDiff); + } + } +} + + +status_t +AudioResampler::Read(void* buffer, int64 pos, int64 frames) +{ + TRACE("AudioResampler::Read(%p, %Ld, %Ld)\n", buffer, pos, frames); + + status_t error = InitCheck(); + if (error != B_OK) { + TRACE("AudioResampler::Read() done1\n"); + return error; + } + // calculate position and frames in the source data + int64 sourcePos = ConvertToSource(pos); + int64 sourceFrames = ConvertToSource(pos + frames) - sourcePos; + // check the frame counts + if (sourceFrames == frames) { + TRACE("AudioResampler::Read() done2\n"); + return fSource->Read(buffer, sourcePos, sourceFrames); + } + if (sourceFrames == 0) { + ReadSilence(buffer, frames); + TRACE("AudioResampler::Read() done3\n"); + return B_OK; + } + // check, if playing backwards + bool backward = false; + if (sourceFrames < 0) { + sourceFrames = -sourceFrames; + sourcePos -= sourceFrames; + backward = true; + } + + // we need at least two frames to interpolate + sourceFrames += 2; + int32 sampleSize = media_raw_audio_format::B_AUDIO_SIZE_MASK; + uint32 channelCount = fFormat.u.raw_audio.channel_count; + char* inBuffer = new char[sourceFrames * channelCount * sampleSize]; + error = fSource->Read(inBuffer, sourcePos, sourceFrames); + if (error != B_OK) { + TRACE("AudioResampler::_ReadLinear() done4\n"); + return error; + } + double inFrameRate = fSource->Format().u.raw_audio.frame_rate; + double outFrameRate = (double)fFormat.u.raw_audio.frame_rate + / (double)fTimeScale; + // choose the sample buffer to be used + switch (fFormat.u.raw_audio.format) { + case media_raw_audio_format::B_AUDIO_FLOAT: + resample_linear< FloatSampleBuffer >(inBuffer, buffer, + channelCount, inFrameRate, outFrameRate, (int32)frames); + break; + case media_raw_audio_format::B_AUDIO_INT: + resample_linear< IntSampleBuffer >(inBuffer, buffer, + channelCount, inFrameRate, outFrameRate, (int32)frames); + break; + case media_raw_audio_format::B_AUDIO_SHORT: + resample_linear< ShortSampleBuffer >(inBuffer, buffer, + channelCount, inFrameRate, outFrameRate, (int32)frames); + break; + case media_raw_audio_format::B_AUDIO_UCHAR: + resample_linear< UCharSampleBuffer >(inBuffer, buffer, + channelCount, inFrameRate, outFrameRate, (int32)frames); + break; + case media_raw_audio_format::B_AUDIO_CHAR: + resample_linear< CharSampleBuffer >(inBuffer, buffer, + channelCount, inFrameRate, outFrameRate, (int32)frames); + break; + } + // reverse the frame order if reading backwards + if (backward) + ReverseFrames(buffer, frames); + delete[] inBuffer; + TRACE("AudioResampler::Read() done\n"); + return B_OK; +} + + +status_t +AudioResampler::InitCheck() const +{ + status_t error = AudioReader::InitCheck(); + if (error == B_OK && !fSource) + error = B_NO_INIT; + return error; +} + + +void +AudioResampler::SetSource(AudioReader* source) +{ + if (!source) { + TRACE("AudioResampler::SetSource() - NULL source\n"); + return; + } + + if (source->Format().type != B_MEDIA_RAW_AUDIO) { + TRACE("AudioResampler::SetSource() - not B_MEDIA_RAW_AUDIO\n"); + return; + } + + uint32 hostByteOrder + = (B_HOST_IS_BENDIAN) ? B_MEDIA_BIG_ENDIAN : B_MEDIA_LITTLE_ENDIAN; + if (source->Format().u.raw_audio.byte_order != hostByteOrder) { + TRACE("AudioResampler::SetSource() - not host byte order\n"); + return; + } + + float frameRate = FrameRate(); + // don't overwrite previous audio frame rate + fSource = source; + fFormat = source->Format(); + fFormat.u.raw_audio.frame_rate = frameRate; +} + + +void +AudioResampler::SetFrameRate(float frameRate) +{ + fFormat.u.raw_audio.frame_rate = frameRate; +} + + +void +AudioResampler::SetTimeScale(float timeScale) +{ + fTimeScale = timeScale; +} + + +AudioReader* +AudioResampler::Source() const +{ + return fSource; +} + + +float +AudioResampler::FrameRate() const +{ + return fFormat.u.raw_audio.frame_rate; +} + + +float +AudioResampler::TimeScale() const +{ + return fTimeScale; +} + + +void +AudioResampler::SetInOffset(int64 offset) +{ + fInOffset = offset; +} + + +int64 +AudioResampler::InOffset() const +{ + return fInOffset; +} + + +int64 +AudioResampler::ConvertFromSource(int64 pos) const +{ + double inFrameRate = fSource->Format().u.raw_audio.frame_rate; + double outFrameRate = fFormat.u.raw_audio.frame_rate; + return (int64)((double)(pos - fInOffset) * outFrameRate / inFrameRate + / (double)fTimeScale) - fOutOffset; +} + + +int64 +AudioResampler::ConvertToSource(int64 pos) const +{ + double inFrameRate = fSource->Format().u.raw_audio.frame_rate; + double outFrameRate = fFormat.u.raw_audio.frame_rate; + return (int64)((double)(pos + fOutOffset) * inFrameRate / outFrameRate + * (double)fTimeScale) + fInOffset; +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioResampler.h b/src/apps/mediaplayer/media_node_framework/audio/AudioResampler.h new file mode 100644 index 0000000000..d88fc45708 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioResampler.h @@ -0,0 +1,52 @@ +/* + * Copyright © 2000-2006 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ + +/*! This AudioReader does both resampling an audio source to a different + sample rate and rescaling the time, e.g. it is possible to convert the + source data from 41.1 KHz to 96 KHz played backward twice as fast + (time scale = -2). +*/ + +#ifndef AUDIO_RESAMPLER_H +#define AUDIO_RESAMPLER_H + +#include "AudioReader.h" + +class AudioResampler : public AudioReader { +public: + AudioResampler(); + AudioResampler(AudioReader* source, + float frameRate, float timeScale = 1.0); + virtual ~AudioResampler(); + + virtual status_t Read(void* buffer, int64 pos, int64 frames); + + virtual status_t InitCheck() const; + + void SetSource(AudioReader* source); + void SetFrameRate(float frameRate); + void SetTimeScale(float timeScale); + + AudioReader* Source() const; + float FrameRate() const; + float TimeScale() const; + + void SetInOffset(int64 offset); + int64 InOffset() const; + + int64 ConvertFromSource(int64 pos) const; + int64 ConvertToSource(int64 pos) const; + +private: + status_t _ReadLinear(void* buffer, int64 pos, + int64 frames); + +private: + AudioReader* fSource; + float fTimeScale; // speed + int64 fInOffset; +}; + +#endif // AUDIO_RESAMPLER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioSupplier.cpp b/src/apps/mediaplayer/media_node_framework/audio/AudioSupplier.cpp new file mode 100644 index 0000000000..08529ed7df --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioSupplier.cpp @@ -0,0 +1,31 @@ +/* Copyright (c) 2000-2008, Ingo Weinhold , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "AudioSupplier.h" + +#include "AudioProducer.h" + + +AudioSupplier::AudioSupplier() +{ +} + + +AudioSupplier::~AudioSupplier() +{ +} + + +void +AudioSupplier::SetAudioProducer(AudioProducer* producer) +{ + fAudioProducer = producer; +} + + +status_t +AudioSupplier::InitCheck() const +{ + return (fAudioProducer ? B_OK : B_NO_INIT); +} + diff --git a/src/apps/mediaplayer/media_node_framework/audio/AudioSupplier.h b/src/apps/mediaplayer/media_node_framework/audio/AudioSupplier.h new file mode 100644 index 0000000000..a6c9fb923a --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/AudioSupplier.h @@ -0,0 +1,36 @@ +/* Copyright (c) 2000-2008, Ingo Weinhold , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ + +/*! This class is an interface used by the AudioProducer to retreive the + audio data to be played. */ +#ifndef AUDIO_SUPPLIER_H +#define AUDIO_SUPPLIER_H + + +#include + + +class AudioProducer; + +class AudioSupplier { + public: + AudioSupplier(); + virtual ~AudioSupplier(); + + virtual void SetAudioProducer(AudioProducer* producer); + + virtual status_t GetFrames(void* buffer, int64 frameCount, + bigtime_t startTime, + bigtime_t endTime) = 0; + + virtual void SetFormat(const media_format& format) = 0; + + virtual status_t InitCheck() const; + + protected: + AudioProducer* fAudioProducer; +}; + + +#endif // AUDIO_SUPPLIER_H diff --git a/src/apps/mediaplayer/media_node_framework/audio/SampleBuffer.h b/src/apps/mediaplayer/media_node_framework/audio/SampleBuffer.h new file mode 100644 index 0000000000..1666820890 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/audio/SampleBuffer.h @@ -0,0 +1,151 @@ +/* + * Copyright © 2000-2003 Ingo Weinhold + * All rights reserved. Distributed under the terms of the MIT licensce. + */ + +// This file contains the following classes: +// * SampleBuffer +// * FloatSampleBuffer +// * IntSampleBuffer +// * ShortSampleBuffer +// * UCharSampleBuffer +// * CharSampleBuffer + +#ifndef SAMPLE_BUFFER_H +#define SAMPLE_BUFFER_H + +#include + +// SampleBuffer + +template +class SampleBuffer { + protected: + typedef uint8 sample_block_t[BYTES_PER_SAMPLE]; + + public: + inline SampleBuffer(void* buffer) + : fBuffer((sample_block_t*)buffer) { } + + inline void operator+=(int samples) { + fBuffer += samples; + } + inline void operator-=(int samples) { + fBuffer -= samples; + } + + inline void operator++(int) { + fBuffer++; + } + inline void operator--(int) { + fBuffer--; + } + + inline void* operator+(int samples) { + return fBuffer + samples; + } + inline void* operator-(int samples) { + return fBuffer + samples; + } + + protected: + sample_block_t* fBuffer; +}; + + +// FloatSampleBuffer + +template +class FloatSampleBuffer : public SampleBuffer { + public: + inline FloatSampleBuffer(void* buffer) + : SampleBuffer(buffer) { + } + + inline sample_t ReadSample() const { + return *((float*)fBuffer); + } + + inline void WriteSample(sample_t value) { + *((float*)fBuffer) = value; + } +}; + + +// IntSampleBuffer + +template +class IntSampleBuffer : public SampleBuffer { + public: + inline IntSampleBuffer(void* buffer) + : SampleBuffer(buffer) { + } + + inline sample_t ReadSample() const { + return (sample_t)*((int*)fBuffer) / (sample_t)0x7fffffff; + } + + inline void WriteSample(sample_t value) { + *((int*)fBuffer) = int(value * (sample_t)0x7fffffff); + } +}; + + +// ShortSampleBuffer + +template +class ShortSampleBuffer : public SampleBuffer { + public: + inline ShortSampleBuffer(void* buffer) + : SampleBuffer(buffer) { + } + + inline sample_t ReadSample() const { + return (sample_t)*((short*)fBuffer) / (sample_t)32767; + } + + inline void WriteSample(sample_t value) { + *((short*)fBuffer) = short(value * (sample_t)32767); + } +}; + + +// UCharSampleBuffer + +template +class UCharSampleBuffer : public SampleBuffer { + public: + inline UCharSampleBuffer(void* buffer) + : SampleBuffer(buffer) { + } + + inline sample_t ReadSample() const { + return ((sample_t)*((uchar*)fBuffer) - 128) / (sample_t)127; + } + + inline void WriteSample(sample_t value) { + *((uchar*)fBuffer) = uchar(value * (sample_t)127 + 128); + } +}; + + +// CharSampleBuffer + +template +class CharSampleBuffer : public SampleBuffer { + public: + inline CharSampleBuffer(void* buffer) + : SampleBuffer(buffer) { + } + + inline sample_t ReadSample() const { + return (sample_t)*((char*)fBuffer) / (sample_t)127; + } + + inline void WriteSample(sample_t value) { + *((uchar*)fBuffer) = uchar(value * (sample_t)127); + } +}; + + +#endif // SAMPLE_BUFFER_H diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.cpp b/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.cpp new file mode 100644 index 0000000000..aec813fd70 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.cpp @@ -0,0 +1,616 @@ +/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "VideoConsumer.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "NodeManager.h" +#include "VideoTarget.h" + + +// debugging +//#define TRACE_VIDEO_CONSUMER +#ifdef TRACE_VIDEO_CONSUMER +# define TRACE(x...) printf(x) +# define PROGRESS(x...) printf(x) +# define FUNCTION(x...) printf(x) +# define LOOP(x...) printf(x) +# define ERROR(x...) fprintf(stderr, x) +#else +# define TRACE(x...) +# define PROGRESS(x...) +# define FUNCTION(x...) +# define LOOP(x...) +# define ERROR(x...) fprintf(stderr, x) +#endif + +#define M1 ((double)1000000.0) +#define JITTER 20000 + + +VideoConsumer::VideoConsumer(const char* name, BMediaAddOn* addon, + const uint32 internal_id, NodeManager* manager, + VideoTarget* target) + : BMediaNode(name), + BMediaEventLooper(), + BBufferConsumer(B_MEDIA_RAW_VIDEO), + fInternalID(internal_id), + fAddOn(addon), + fConnectionActive(false), + fMyLatency(20000), + fOurBuffers(false), + fBuffers(NULL), + fManager(manager), + fTargetLock(), + fTarget(target), + fTargetBufferIndex(-1) +{ + FUNCTION("VideoConsumer::VideoConsumer\n"); + + AddNodeKind(B_PHYSICAL_OUTPUT); + SetEventLatency(0); + + for (uint32 i = 0; i < kBufferCount; i++) { + fBitmap[i] = NULL; + fBufferMap[i] = 0; + } + + SetPriority(B_DISPLAY_PRIORITY); +} + + +VideoConsumer::~VideoConsumer() +{ + Quit(); + DeleteBuffers(); +} + + +BMediaAddOn* +VideoConsumer::AddOn(long* cookie) const +{ + FUNCTION("VideoConsumer::AddOn\n"); + // do the right thing if we're ever used with an add-on + *cookie = fInternalID; + return fAddOn; +} + + +// This implementation is required to get around a bug in +// the ppc compiler. +void +VideoConsumer::Start(bigtime_t performance_time) +{ + BMediaEventLooper::Start(performance_time); +} + + +void +VideoConsumer::Stop(bigtime_t performance_time, bool immediate) +{ + BMediaEventLooper::Stop(performance_time, immediate); +} + + +void +VideoConsumer::Seek(bigtime_t media_time, bigtime_t performance_time) +{ + BMediaEventLooper::Seek(media_time, performance_time); +} + + +void +VideoConsumer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time) +{ + BMediaEventLooper::TimeWarp(at_real_time, to_performance_time); +} + + +void +VideoConsumer::NodeRegistered() +{ + FUNCTION("VideoConsumer::NodeRegistered\n"); + fIn.destination.port = ControlPort(); + fIn.destination.id = 0; + fIn.source = media_source::null; + fIn.format.type = B_MEDIA_RAW_VIDEO; + // wild cards yet + fIn.format.u.raw_video = media_raw_video_format::wildcard; + fIn.format.u.raw_video.interlace = 1; + fIn.format.u.raw_video.display.format = B_NO_COLOR_SPACE; + fIn.format.u.raw_video.display.bytes_per_row = 0; + fIn.format.u.raw_video.display.line_width = 0; + fIn.format.u.raw_video.display.line_count = 0; + + Run(); +} + + +status_t +VideoConsumer::RequestCompleted(const media_request_info& info) +{ + FUNCTION("VideoConsumer::RequestCompleted\n"); + switch(info.what) { + case media_request_info::B_SET_OUTPUT_BUFFERS_FOR: + if (info.status != B_OK) + ERROR("VideoConsumer::RequestCompleted: Not using our " + "buffers!\n"); + break; + + default: + break; + } + return B_OK; +} + + +status_t +VideoConsumer::HandleMessage(int32 message, const void* data, size_t size) +{ + return B_OK; +} + + +void +VideoConsumer::BufferReceived(BBuffer* buffer) +{ + LOOP("VideoConsumer::Buffer #%ld received\n", buffer->ID()); + + if (RunState() == B_STOPPED) { + buffer->Recycle(); + return; + } + + media_timed_event event(buffer->Header()->start_time, + BTimedEventQueue::B_HANDLE_BUFFER, buffer, + BTimedEventQueue::B_RECYCLE_BUFFER); + EventQueue()->AddEvent(event); +} + + +void +VideoConsumer::ProducerDataStatus(const media_destination& forWhom, + int32 status, bigtime_t atMediaTime) +{ + FUNCTION("VideoConsumer::ProducerDataStatus\n"); + + if (forWhom != fIn.destination) + return; +} + + +status_t +VideoConsumer::CreateBuffers(const media_format& format) +{ + FUNCTION("VideoConsumer::CreateBuffers\n"); + + // delete any old buffers + DeleteBuffers(); + + status_t status = B_OK; + + // create a buffer group + uint32 width = format.u.raw_video.display.line_width; + uint32 height = format.u.raw_video.display.line_count; + color_space colorSpace = format.u.raw_video.display.format; + PROGRESS("VideoConsumer::CreateBuffers - Colorspace = %d\n", colorSpace); + + fBuffers = new BBufferGroup(); + status = fBuffers->InitCheck(); + if (B_OK != status) { + ERROR("VideoConsumer::CreateBuffers - ERROR CREATING BUFFER GROUP\n"); + return status; + } + + // and attach the bitmaps to the buffer group + BRect bounds(0, 0, width - 1, height - 1); + for (uint32 i = 0; i < kBufferCount; i++) { + // figure out the bitmap creation flags + uint32 bitmapFlags = 0; + if (colorSpace == B_YCbCr422 || colorSpace == B_YCbCr444) { + // try to use hardware overlay + bitmapFlags |= B_BITMAP_WILL_OVERLAY; + if (i == 0) + bitmapFlags |= B_BITMAP_RESERVE_OVERLAY_CHANNEL; + } else + bitmapFlags = B_BITMAP_IS_LOCKED; + + fBitmap[i] = new BBitmap(bounds, bitmapFlags, colorSpace); + status = fBitmap[i]->InitCheck(); + if (status >= B_OK) { + buffer_clone_info info; + + uint8* bits = (uint8*)fBitmap[i]->Bits(); + info.area = area_for(bits); + area_info bitmapAreaInfo; + status = get_area_info(info.area, &bitmapAreaInfo); + if (status != B_OK) { + fprintf(stderr, "VideoConsumer::CreateBuffers() - " + "get_area_info(): %s\n", strerror(status)); + return status; + } + +//printf("area info for bitmap %ld (%p):\n", i, fBitmap[i]->Bits()); +//printf(" area: %ld\n", bitmapAreaInfo.area); +//printf(" size: %ld\n", bitmapAreaInfo.size); +//printf(" lock: %ld\n", bitmapAreaInfo.lock); +//printf(" protection: %ld\n", bitmapAreaInfo.protection); +//printf(" ram size: %ld\n", bitmapAreaInfo.ram_size); +//printf(" copy_count: %ld\n", bitmapAreaInfo.copy_count); +//printf(" out_count: %ld\n", bitmapAreaInfo.out_count); +//printf(" address: %p\n", bitmapAreaInfo.address); + + info.offset = bits - (uint8*)bitmapAreaInfo.address; + info.size = (size_t)fBitmap[i]->BitsLength(); + info.flags = 0; + info.buffer = 0; + + BBuffer *buffer = NULL; + if ((status = fBuffers->AddBuffer(info, &buffer)) != B_OK) { + ERROR("VideoConsumer::CreateBuffers - ERROR ADDING BUFFER TO GROUP\n"); + return status; + } else + PROGRESS("VideoConsumer::CreateBuffers - SUCCESSFUL ADD BUFFER TO GROUP\n"); + } else { + ERROR("VideoConsumer::CreateBuffers - ERROR CREATING VIDEO RING BUFFER: %s\n", strerror(status)); + return status; + } + } + + BBuffer** buffList = new BBuffer*[kBufferCount]; + for (uint32 i = 0; i < kBufferCount; i++) + buffList[i] = 0; + + if ((status = fBuffers->GetBufferList(kBufferCount, buffList)) == B_OK) { + for (uint32 i = 0; i < kBufferCount; i++) { + if (buffList[i] != NULL) { + fBufferMap[i] = (uint32)buffList[i]; + PROGRESS(" i = %lu buffer = %08lx\n", i, fBufferMap[i]); + } else { + ERROR("VideoConsumer::CreateBuffers ERROR MAPPING RING BUFFER\n"); + return B_ERROR; + } + } + } else + ERROR("VideoConsumer::CreateBuffers ERROR IN GET BUFFER LIST\n"); + + FUNCTION("VideoConsumer::CreateBuffers - EXIT\n"); + return status; +} + + +void +VideoConsumer::DeleteBuffers() +{ + FUNCTION("VideoConsumer::DeleteBuffers\n"); + if (fBuffers) { + fTargetLock.Lock(); + if (fTargetBufferIndex >= 0) { + if (fTarget) + fTarget->SetBitmap(NULL); + fTargetBufferIndex = -1; + } + fTargetLock.Unlock(); + + delete fBuffers; + fBuffers = NULL; + + for (uint32 i = 0; i < kBufferCount; i++) { + snooze(20000); + delete fBitmap[i]; + fBitmap[i] = NULL; + } + } + FUNCTION("VideoConsumer::DeleteBuffers - EXIT\n"); +} + + +void +VideoConsumer::SetTarget(VideoTarget* target) +{ + fTargetLock.Lock(); + if (fTarget) + fTarget->SetBitmap(NULL); + fTarget = target; + if (fTarget && fTargetBufferIndex >= 0) + fTarget->SetBitmap(fBitmap[fTargetBufferIndex]); + fTargetLock.Unlock(); +} + + +status_t +VideoConsumer::Connected(const media_source& producer, + const media_destination& where, const media_format& format, + media_input* outInput) +{ + FUNCTION("VideoConsumer::Connected\n"); + + fIn.source = producer; + fIn.format = format; + fIn.node = Node(); + sprintf(fIn.name, "Video Consumer"); + *outInput = fIn; + + uint32 user_data = 0; + int32 change_tag = 1; + if (CreateBuffers(format) == B_OK) { + BBufferConsumer::SetOutputBuffersFor(producer, fDestination, + fBuffers, (void *)&user_data, + &change_tag, true); + fIn.format.u.raw_video.display.bytes_per_row = fBitmap[0]->BytesPerRow(); + } else { + ERROR("VideoConsumer::Connected - COULDN'T CREATE BUFFERS\n"); + return B_ERROR; + } + + *outInput = fIn; + // bytes per row might have changed + fConnectionActive = true; + + FUNCTION("VideoConsumer::Connected - EXIT\n"); + return B_OK; +} + + +void +VideoConsumer::Disconnected(const media_source& producer, + const media_destination& where) +{ + FUNCTION("VideoConsumer::Disconnected\n"); + + if (where != fIn.destination || producer != fIn.source) + return; + + // reclaim our buffers + int32 changeTag = 0; + BBufferConsumer::SetOutputBuffersFor(producer, fDestination, NULL, + NULL, &changeTag, false); + if (fOurBuffers) { + status_t reclaimError = fBuffers->ReclaimAllBuffers(); + if (reclaimError != B_OK) { + fprintf(stderr, "VideoConsumer::Disconnected() - Failed to " + "reclaim our buffers: %s\n", strerror(reclaimError)); + } + } + // disconnect the connection + fIn.source = media_source::null; + fConnectionActive = false; + + // Unset the target's bitmap. Just to be safe -- since it is usually + // done when the stop event arrives, but someone may disonnect + // without stopping us before. + fTargetLock.Lock(); + if (fTargetBufferIndex >= 0) { + if (fTarget) + fTarget->SetBitmap(NULL); + if (fOurBuffers) + ((BBuffer*)fBufferMap[fTargetBufferIndex])->Recycle(); + fTargetBufferIndex = -1; + } + fTargetLock.Unlock(); +} + + +status_t +VideoConsumer::AcceptFormat(const media_destination& dest, media_format* format) +{ + FUNCTION("VideoConsumer::AcceptFormat\n"); + + if (dest != fIn.destination) { + ERROR("VideoConsumer::AcceptFormat - BAD DESTINATION\n"); + return B_MEDIA_BAD_DESTINATION; + } + + if (format->type == B_MEDIA_NO_TYPE) + format->type = B_MEDIA_RAW_VIDEO; + + if (format->type != B_MEDIA_RAW_VIDEO) { + ERROR("VideoConsumer::AcceptFormat - BAD FORMAT\n"); + return B_MEDIA_BAD_FORMAT; + } + + if (format->u.raw_video.display.format != B_YCbCr444 && + format->u.raw_video.display.format != B_YCbCr422 && + format->u.raw_video.display.format != B_RGB32 && + format->u.raw_video.display.format != B_RGB16 && + format->u.raw_video.display.format != B_RGB15 && + format->u.raw_video.display.format != B_GRAY8 && + format->u.raw_video.display.format + != media_raw_video_format::wildcard.display.format) { + ERROR("AcceptFormat - not a format we know about!\n"); + return B_MEDIA_BAD_FORMAT; + } + + char string[256]; + string[0] = 0; + string_for_format(*format, string, 256); + FUNCTION("VideoConsumer::AcceptFormat: %s\n", string); + + return B_OK; +} + + +status_t +VideoConsumer::GetNextInput(int32* cookie, media_input* outInput) +{ + FUNCTION("VideoConsumer::GetNextInput\n"); + + // custom build a destination for this connection + // put connection number in id + + if (*cookie < 1) { + fIn.node = Node(); + fIn.destination.id = *cookie; + sprintf(fIn.name, "Video Consumer"); + *outInput = fIn; + (*cookie)++; + return B_OK; + } else { + return B_MEDIA_BAD_DESTINATION; + } +} + + +void +VideoConsumer::DisposeInputCookie(int32 /*cookie*/) +{ +} + + +status_t +VideoConsumer::GetLatencyFor( + const media_destination &for_whom, + bigtime_t * out_latency, + media_node_id * out_timesource) +{ + FUNCTION("VideoConsumer::GetLatencyFor\n"); + + if (for_whom != fIn.destination) + return B_MEDIA_BAD_DESTINATION; + + *out_latency = fMyLatency; + *out_timesource = TimeSource()->ID(); + return B_OK; +} + + +status_t +VideoConsumer::FormatChanged(const media_source& producer, + const media_destination& consumer, int32 fromChangeCount, + const media_format& format) +{ + FUNCTION("VideoConsumer::FormatChanged\n"); + + if (consumer != fIn.destination) + return B_MEDIA_BAD_DESTINATION; + + if (producer != fIn.source) + return B_MEDIA_BAD_SOURCE; + + fIn.format = format; + + return CreateBuffers(format); +} + + +void +VideoConsumer::HandleEvent(const media_timed_event* event, bigtime_t lateness, + bool realTimeEvent) +{ + LOOP("VideoConsumer::HandleEvent\n"); + + BBuffer* buffer; + + switch (event->type) { + case BTimedEventQueue::B_START: + PROGRESS("VideoConsumer::HandleEvent - START\n"); + break; + + case BTimedEventQueue::B_STOP: + PROGRESS("VideoConsumer::HandleEvent - STOP\n"); + EventQueue()->FlushEvents(event->event_time, BTimedEventQueue::B_ALWAYS, true, BTimedEventQueue::B_HANDLE_BUFFER); + // unset the target's bitmap + fTargetLock.Lock(); + if (fTargetBufferIndex >= 0) { + if (fTarget) + fTarget->SetBitmap(NULL); + if (fOurBuffers) + ((BBuffer*)fBufferMap[fTargetBufferIndex])->Recycle(); + fTargetBufferIndex = -1; + } + fTargetLock.Unlock(); + break; + + case BTimedEventQueue::B_HANDLE_BUFFER: + LOOP("VideoConsumer::HandleEvent - HANDLE BUFFER\n"); + buffer = (BBuffer *) event->pointer; + + if (RunState() == B_STARTED && fConnectionActive) { + // see if this is one of our buffers + uint32 index = 0; + fOurBuffers = true; + while (index < kBufferCount) { + if ((uint32)buffer == fBufferMap[index]) + break; + else + index++; + } + if (index == kBufferCount) { + // no, buffers belong to consumer + fOurBuffers = false; + index = (fTargetBufferIndex + 1) % kBufferCount; + } + + bool dropped = false; + bool recycle = true; + if ((RunMode() == B_OFFLINE) +// TODO: Fix the runmode stuff! Setting the consumer to B_OFFLINE does +// not do the trick. I made the VideoConsumer check the performance +// time of the buffer and if it is 0, it plays it regardless. +|| (buffer->Header()->start_time == 2 * fMyLatency + SchedulingLatency()) + || (/*(TimeSource()->Now() + > (buffer->Header()->start_time - JITTER)) &&*/ + (TimeSource()->Now() < (buffer->Header()->start_time + + JITTER)))) { + if (!fOurBuffers) { + memcpy(fBitmap[index]->Bits(), buffer->Data(), + fBitmap[index]->BitsLength()); + } + + fTargetLock.Lock(); + if (fTarget) { + fTarget->SetBitmap(fBitmap[index]); + if (fOurBuffers) { + // recycle the previous but not the current buffer + if (fTargetBufferIndex >= 0) { + ((BBuffer*)fBufferMap[fTargetBufferIndex]) + ->Recycle(); + } + recycle = false; + } + fTargetBufferIndex = index; + } + fTargetLock.Unlock(); + } else { + dropped = true; + PROGRESS("VideoConsumer::HandleEvent - DROPPED FRAME\n" + " start_time: %lld, current: %lld, latency: %lld\n", + buffer->Header()->start_time, TimeSource()->Now(), + SchedulingLatency()); + } + if (dropped) { + if (fManager->LockWithTimeout(10000) == B_OK) { + fManager->FrameDropped(); + fManager->Unlock(); + } + } + if (recycle) + buffer->Recycle(); + } else { + TRACE("RunState() != B_STARTED\n"); + buffer->Recycle(); + } + break; + default: + ERROR("VideoConsumer::HandleEvent - BAD EVENT\n"); + break; + } +} diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.h b/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.h new file mode 100644 index 0000000000..67e03a09eb --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoConsumer.h @@ -0,0 +1,124 @@ +/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef VIDEO_CONSUMER_H +#define VIDEO_CONSUMER_H + + +#include +#include +#include + + +class BBitmap; +class NodeManager; +class VideoTarget; + + +// TODO: The buffer count should depend on the latency! +static const unsigned int kBufferCount = 4; + + +class VideoConsumer : public BMediaEventLooper, public BBufferConsumer { +public: + VideoConsumer( + const char* name, + BMediaAddOn* addon, + const uint32 internal_id, + NodeManager* manager, + VideoTarget* target); + ~VideoConsumer(); + + // BMediaNode interface +public: + + virtual BMediaAddOn* AddOn(long* cookie) const; + + protected: + + virtual void Start(bigtime_t performanceTime); + virtual void Stop(bigtime_t performanceTime, + bool immediate); + virtual void Seek(bigtime_t mediaTime, + bigtime_t performanceTime); + virtual void TimeWarp(bigtime_t atRealTime, + bigtime_t toPerformanceTime); + + virtual void NodeRegistered(); + virtual status_t RequestCompleted( + const media_request_info& info); + + virtual status_t HandleMessage(int32 message, const void* data, + size_t size); + + // BMediaEventLooper interface +protected: + virtual void HandleEvent(const media_timed_event* event, + bigtime_t lateness, bool realTimeEvent); + + // BBufferConsumer interface +public: + virtual status_t AcceptFormat(const media_destination& dest, + media_format* format); + virtual status_t GetNextInput(int32* cookie, + media_input* _input); + + virtual void DisposeInputCookie(int32 cookie); + +protected: + virtual void BufferReceived(BBuffer* buffer); + +private: + virtual void ProducerDataStatus( + const media_destination& forWhom, + int32 status, + bigtime_t atMediaTime); + virtual status_t GetLatencyFor( + const media_destination& forWhom, + bigtime_t* outLatency, + media_node_id* outId); + virtual status_t Connected(const media_source& producer, + const media_destination& where, + const media_format& withFormat, + media_input* outInput); + virtual void Disconnected(const media_source& producer, + const media_destination& where); + virtual status_t FormatChanged(const media_source& producer, + const media_destination& consumer, + int32 from_change_count, + const media_format& format); + + // VideoConsumer +public: + status_t CreateBuffers( + const media_format& withFormat); + + void DeleteBuffers(); + + void SetTarget(VideoTarget* target); + + private: + uint32 fInternalID; + BMediaAddOn* fAddOn; + + bool fConnectionActive; + media_input fIn; + media_destination fDestination; + bigtime_t fMyLatency; + + BBitmap* fBitmap[kBufferCount]; + bool fOurBuffers; + BBufferGroup* fBuffers; + uint32 fBufferMap[kBufferCount]; + + NodeManager* fManager; + BLocker fTargetLock; // locks the following variable + VideoTarget* volatile fTarget; + int32 fTargetBufferIndex; +}; + +#endif // VIDEO_CONSUMER_H diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp b/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp new file mode 100644 index 0000000000..e39479f208 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoProducer.cpp @@ -0,0 +1,851 @@ +/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "VideoProducer.h" + +#include +#include + +#include +#include +#include +#include + +#include "NodeManager.h" +#include "VideoSupplier.h" + + +// debugging +//#define TRACE_VIDEO_PRODUCER +#ifdef TRACE_VIDEO_PRODUCER +# define TRACE(x...) printf("VideoProducer::"); printf(x) +# define FUNCTION(x...) TRACE(x) +# define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x) +#else +# define TRACE(x...) +# define FUNCTION(x...) +# define ERROR(x...) fprintf(stderr, "VideoProducer::"); fprintf(stderr, x) +#endif + + +#define BUFFER_COUNT 3 + +#define TOUCH(x) ((void)(x)) + + +VideoProducer::VideoProducer(BMediaAddOn* addon, const char* name, + int32 internalId, NodeManager* manager, VideoSupplier* supplier) + : BMediaNode(name), + BMediaEventLooper(), + BBufferProducer(B_MEDIA_RAW_VIDEO), + fInitStatus(B_NO_INIT), + fInternalID(internalId), + fAddOn(addon), + fBufferGroup(NULL), + fUsedBufferGroup(NULL), + fThread(-1), + fFrameSync(-1), + fRunning(false), + fConnected(false), + fEnabled(false), + fManager(manager), + fSupplier(supplier) +{ + fOutput.destination = media_destination::null; + fInitStatus = B_OK; +} + + +VideoProducer::~VideoProducer() +{ + if (fInitStatus == B_OK) { + // Clean up after ourselves, in case the application didn't make us + // do so. + if (fConnected) + Disconnect(fOutput.source, fOutput.destination); + if (fRunning) + _HandleStop(); + } + Quit(); +} + + +port_id +VideoProducer::ControlPort() const +{ + return BMediaNode::ControlPort(); +} + + +BMediaAddOn* +VideoProducer::AddOn(int32* _internalId) const +{ + if (_internalId) + *_internalId = fInternalID; + return fAddOn; +} + + +status_t +VideoProducer::HandleMessage(int32 message, const void* data, size_t size) +{ + return B_ERROR; +} + + +void +VideoProducer::SetTimeSource(BTimeSource* timeSource) +{ + // Tell frame generation thread to recalculate delay value + release_sem(fFrameSync); +} + + +status_t +VideoProducer::RequestCompleted(const media_request_info& info) +{ + return BMediaNode::RequestCompleted(info); +} + + +void +VideoProducer::NodeRegistered() +{ + if (fInitStatus != B_OK) { + ReportError(B_NODE_IN_DISTRESS); + return; + } + + fOutput.node = Node(); + fOutput.source.port = ControlPort(); + fOutput.source.id = 0; + fOutput.destination = media_destination::null; + strcpy(fOutput.name, Name()); + + // fill with wild cards at this point in time + fOutput.format.type = B_MEDIA_RAW_VIDEO; + fOutput.format.u.raw_video = media_raw_video_format::wildcard; + fOutput.format.u.raw_video.interlace = 1; + fOutput.format.u.raw_video.display.format = B_NO_COLOR_SPACE; + fOutput.format.u.raw_video.display.bytes_per_row = 0; + fOutput.format.u.raw_video.display.line_width = 0; + fOutput.format.u.raw_video.display.line_count = 0; + + // start the BMediaEventLooper control loop running + Run(); +} + + +void +VideoProducer::Start(bigtime_t performanceTime) +{ + // notify the manager in case we were started from the outside world +// fManager->StartPlaying(); + + BMediaEventLooper::Start(performanceTime); +} + + +void +VideoProducer::Stop(bigtime_t performanceTime, bool immediate) +{ + // notify the manager in case we were stopped from the outside world +// fManager->StopPlaying(); + + BMediaEventLooper::Stop(performanceTime, immediate); +} + + +void +VideoProducer::Seek(bigtime_t media_time, bigtime_t performanceTime) +{ + BMediaEventLooper::Seek(media_time, performanceTime); +} + + +void +VideoProducer::TimeWarp(bigtime_t at_real_time, bigtime_t to_performance_time) +{ + BMediaEventLooper::TimeWarp(at_real_time, to_performance_time); +} + + +status_t +VideoProducer::AddTimer(bigtime_t at_performance_time, int32 cookie) +{ + return BMediaEventLooper::AddTimer(at_performance_time, cookie); +} + + +void +VideoProducer::SetRunMode(run_mode mode) +{ +printf("VideoProducer::SetRunMode(%d)\n", mode); + TRACE("SetRunMode(%d)\n", mode); + BMediaEventLooper::SetRunMode(mode); +} + + +void +VideoProducer::HandleEvent(const media_timed_event* event, + bigtime_t lateness, bool realTimeEvent) +{ + TOUCH(lateness); TOUCH(realTimeEvent); + + switch(event->type) { + case BTimedEventQueue::B_START: + _HandleStart(event->event_time); + break; + case BTimedEventQueue::B_STOP: + _HandleStop(); + break; + case BTimedEventQueue::B_WARP: + _HandleTimeWarp(event->bigdata); + break; + case BTimedEventQueue::B_SEEK: + _HandleSeek(event->bigdata); + break; + case BTimedEventQueue::B_HANDLE_BUFFER: + case BTimedEventQueue::B_DATA_STATUS: + case BTimedEventQueue::B_PARAMETER: + default: + TRACE("HandleEvent: Unhandled event -- %lx\n", event->type); + break; + } +} + + +void +VideoProducer::CleanUpEvent(const media_timed_event *event) +{ + BMediaEventLooper::CleanUpEvent(event); +} + + +bigtime_t +VideoProducer::OfflineTime() +{ + return BMediaEventLooper::OfflineTime(); +} + + +void +VideoProducer::ControlLoop() +{ + BMediaEventLooper::ControlLoop(); +} + + +status_t +VideoProducer::DeleteHook(BMediaNode* node) +{ + return BMediaEventLooper::DeleteHook(node); +} + + +status_t +VideoProducer::FormatSuggestionRequested(media_type type, int32 quality, + media_format* _format) +{ + FUNCTION("FormatSuggestionRequested\n"); + + if (type != B_MEDIA_ENCODED_VIDEO) + return B_MEDIA_BAD_FORMAT; + + TOUCH(quality); + + *_format = fOutput.format; + return B_OK; +} + + +status_t +VideoProducer::FormatProposal(const media_source& output, media_format* format) +{ + #ifdef TRACE_VIDEO_PRODUCER + char string[256]; + string_for_format(*format, string, 256); + FUNCTION("FormatProposal(%s)\n", string); + #endif + + if (!format) + return B_BAD_VALUE; + + if (output != fOutput.source) + return B_MEDIA_BAD_SOURCE; + + status_t ret = format_is_compatible(*format, fOutput.format) ? + B_OK : B_MEDIA_BAD_FORMAT; + if (ret != B_OK) + ERROR("FormatProposal() error\n"); + // change any wild cards to specific values + + return ret; + +} + + +status_t +VideoProducer::FormatChangeRequested(const media_source& source, + const media_destination& destination, media_format* ioFormat, + int32 *_deprecated_) +{ + TOUCH(destination); TOUCH(ioFormat); TOUCH(_deprecated_); + + if (source != fOutput.source) + return B_MEDIA_BAD_SOURCE; + + return B_ERROR; +} + + +status_t +VideoProducer::GetNextOutput(int32* cookie, media_output* outOutput) +{ + if (!outOutput) + return B_BAD_VALUE; + + if ((*cookie) != 0) + return B_BAD_INDEX; + + *outOutput = fOutput; + (*cookie)++; + + return B_OK; +} + + +status_t +VideoProducer::DisposeOutputCookie(int32 cookie) +{ + TOUCH(cookie); + + return B_OK; +} + + +status_t +VideoProducer::SetBufferGroup(const media_source& forSource, + BBufferGroup *group) +{ + if (forSource != fOutput.source) + return B_MEDIA_BAD_SOURCE; + + TRACE("VideoProducer::SetBufferGroup() - using buffer group of " + "consumer.\n"); + fUsedBufferGroup = group; + + return B_OK; +} + + +status_t +VideoProducer::VideoClippingChanged(const media_source& forSource, + int16 numShorts, int16* clipData, const media_video_display_info& display, + int32* _deprecated_) +{ + TOUCH(forSource); TOUCH(numShorts); TOUCH(clipData); + TOUCH(display); TOUCH(_deprecated_); + + return B_ERROR; +} + + +status_t +VideoProducer::GetLatency(bigtime_t* _latency) +{ + if (!_latency) + return B_BAD_VALUE; + + *_latency = EventLatency() + SchedulingLatency(); + + return B_OK; +} + + +status_t +VideoProducer::PrepareToConnect(const media_source& source, + const media_destination& destination, media_format* format, + media_source* outSource, char* outName) +{ + FUNCTION("PrepareToConnect() %ldx%ld\n", + format->u.raw_video.display.line_width, + format->u.raw_video.display.line_count); + + if (fConnected) { + ERROR("PrepareToConnect() - already connected!\n"); + return B_MEDIA_ALREADY_CONNECTED; + } + + if (source != fOutput.source) + return B_MEDIA_BAD_SOURCE; + + if (fOutput.destination != media_destination::null) { + ERROR("PrepareToConnect() - destination != null.\n"); + return B_MEDIA_ALREADY_CONNECTED; + } + + // The format parameter comes in with the suggested format, and may be + // specialized as desired by the node + if (!format_is_compatible(*format, fOutput.format)) { + ERROR("PrepareToConnect() - incompatible format.\n"); + *format = fOutput.format; + return B_MEDIA_BAD_FORMAT; + } + + if (format->u.raw_video.display.line_width == 0) + format->u.raw_video.display.line_width = 384; + if (format->u.raw_video.display.line_count == 0) + format->u.raw_video.display.line_count = 288; + if (format->u.raw_video.field_rate == 0) + format->u.raw_video.field_rate = 25.0; + if (format->u.raw_video.display.bytes_per_row == 0) + format->u.raw_video.display.bytes_per_row = format->u.raw_video.display.line_width * 4; + + *outSource = fOutput.source; + strcpy(outName, fOutput.name); + + return B_OK; +} + + +#define NODE_LATENCY 20000 + + +void +VideoProducer::Connect(status_t error, const media_source& source, + const media_destination& destination, const media_format& format, + char* _name) +{ + FUNCTION("Connect() %ldx%ld\n", + format.u.raw_video.display.line_width, + format.u.raw_video.display.line_count); + + if (fConnected) { + ERROR("Connect() - already connected.\n"); + return; + } + + if (source != fOutput.source) { + ERROR("Connect() - wrong source.\n"); + return; + } + if (error < B_OK) { + ERROR("Connect() - consumer error: %s\n", strerror(error)); + return; + } + if (!const_cast(&format)->Matches(&fOutput.format)) { + ERROR("Connect() - format mismatch.\n"); + return; + } + + fOutput.destination = destination; + strcpy(_name, fOutput.name); + + if (fOutput.format.u.raw_video.field_rate != 0.0f) { + fPerformanceTimeBase = fPerformanceTimeBase + + (bigtime_t)((fFrame - fFrameBase) + * 1000000 / fOutput.format.u.raw_video.field_rate); + fFrameBase = fFrame; + } + + fConnectedFormat = format.u.raw_video; + if (fConnectedFormat.display.bytes_per_row == 0) { + ERROR("Connect() - connected format still has BPR wildcard!\n"); + fConnectedFormat.display.bytes_per_row + = 4 * fConnectedFormat.display.line_width; + } + + // get the latency + bigtime_t latency = 0; + media_node_id tsID = 0; + FindLatencyFor(fOutput.destination, &latency, &tsID); + SetEventLatency(latency + NODE_LATENCY); + + // Create the buffer group + if (!fUsedBufferGroup) { + fBufferGroup = new BBufferGroup(fConnectedFormat.display.bytes_per_row + * fConnectedFormat.display.line_count, BUFFER_COUNT); + status_t err = fBufferGroup->InitCheck(); + if (err < B_OK) { + delete fBufferGroup; + fBufferGroup = NULL; + ERROR("Connect() - buffer group error: %s\n", strerror(err)); + return; + } + fUsedBufferGroup = fBufferGroup; + } + + fConnected = true; + fEnabled = true; + + // Tell frame generation thread to recalculate delay value + release_sem(fFrameSync); +} + + +void +VideoProducer::Disconnect(const media_source& source, + const media_destination& destination) +{ + FUNCTION("Disconnect()\n"); + + if (!fConnected) { + ERROR("Disconnect() - Not connected\n"); + return; + } + + if ((source != fOutput.source) || (destination != fOutput.destination)) { + ERROR("Disconnect() - Bad source and/or destination\n"); + return; + } + + fEnabled = false; + fOutput.destination = media_destination::null; + + if (fLock.Lock()) { + // Always delete the buffer group, even if it is not ours. + // (See BeBook::SetBufferGroup()). + delete fUsedBufferGroup; + fUsedBufferGroup = NULL; + fBufferGroup = NULL; + fLock.Unlock(); + } + + fConnected = false; + TRACE("Disconnect() done\n"); +} + + +void +VideoProducer::LateNoticeReceived(const media_source &source, + bigtime_t how_much, bigtime_t performanceTime) +{ + TOUCH(source); TOUCH(how_much); TOUCH(performanceTime); + TRACE("Late!!!\n"); +} + + +void +VideoProducer::EnableOutput(const media_source& source, bool enabled, + int32* _deprecated_) +{ + TOUCH(_deprecated_); + + if (source != fOutput.source) + return; + + fEnabled = enabled; +} + + +status_t +VideoProducer::SetPlayRate(int32 numer, int32 denom) +{ + TOUCH(numer); TOUCH(denom); + + return B_ERROR; +} + + +void +VideoProducer::AdditionalBufferRequested(const media_source& source, + media_buffer_id prevBuffer, bigtime_t prevTime, + const media_seek_tag* prevTag) +{ + TOUCH(source); TOUCH(prevBuffer); TOUCH(prevTime); TOUCH(prevTag); +} + + +void +VideoProducer::LatencyChanged(const media_source& source, + const media_destination& destination, + bigtime_t newLatency, uint32 flags) +{ + TOUCH(source); TOUCH(destination); TOUCH(newLatency); TOUCH(flags); + TRACE("Latency changed!\n"); +} + + +// #pragma mark - + + +void +VideoProducer::_HandleStart(bigtime_t performanceTime) +{ + // Start producing frames, even if the output hasn't been connected yet. + TRACE("_HandleStart(%Ld)\n", performanceTime); + + if (fRunning) { + TRACE("_HandleStart: Node already started\n"); + return; + } + + fFrame = 0; + fFrameBase = 0; + fPerformanceTimeBase = performanceTime; + + fFrameSync = create_sem(0, "frame synchronization"); + if (fFrameSync < B_OK) + return; + + fThread = spawn_thread(_FrameGeneratorThreadEntry, "frame generator", + B_NORMAL_PRIORITY, this); + if (fThread < B_OK) { + delete_sem(fFrameSync); + return; + } + + resume_thread(fThread); + fRunning = true; + return; +} + + +void +VideoProducer::_HandleStop() +{ + TRACE("_HandleStop()\n"); + + if (!fRunning) { + TRACE("_HandleStop: Node isn't running\n"); + return; + } + + delete_sem(fFrameSync); + wait_for_thread(fThread, &fThread); + + fRunning = false; +} + + +void +VideoProducer::_HandleTimeWarp(bigtime_t performanceTime) +{ + fPerformanceTimeBase = performanceTime; + fFrameBase = fFrame; + + // Tell frame generation thread to recalculate delay value + release_sem(fFrameSync); +} + + +void +VideoProducer::_HandleSeek(bigtime_t performanceTime) +{ + fPerformanceTimeBase = performanceTime; + fFrameBase = fFrame; + + // Tell frame generation thread to recalculate delay value + release_sem(fFrameSync); +} + + +int32 +VideoProducer::_FrameGeneratorThreadEntry(void* data) +{ + return ((VideoProducer*)data)->_FrameGeneratorThread(); +} + + +int32 +VideoProducer::_FrameGeneratorThread() +{ + bool forceSendingBuffer = true; + bigtime_t lastFrameSentAt = 0; + int64 lastPlaylistFrame = 0; + bool running = true; + while (running) { + TRACE("_FrameGeneratorThread: loop: %Ld\n", fFrame); + // lock the node manager + status_t err = fManager->LockWithTimeout(10000); + bool ignoreEvent = false; + // Data to be retrieved from the node manager. + bigtime_t performanceTime = 0; + bigtime_t nextPerformanceTime = 0; + bigtime_t waitUntil = 0; + bigtime_t nextWaitUntil = 0; + bigtime_t maxRenderTime = 0; + int32 playingDirection = 0; + int32 playingMode = 0; + int64 playlistFrame = 0; + switch (err) { + case B_OK: { + TRACE("_FrameGeneratorThread: node manager successfully " + "locked\n"); + // get the times for the current and the next frame + performanceTime = fManager->TimeForFrame(fFrame); + nextPerformanceTime = fManager->TimeForFrame(fFrame + 1); + maxRenderTime = min_c(bigtime_t(33334 * 0.9), + max_c(fSupplier->ProcessingLatency(), maxRenderTime)); + playingMode = fManager->PlayModeAtFrame(fFrame); + + waitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase + + performanceTime, 0) - maxRenderTime; + nextWaitUntil = TimeSource()->RealTimeFor(fPerformanceTimeBase + + nextPerformanceTime, 0) - maxRenderTime; + // get playing direction and playlist frame for the current + // frame + bool newPlayingState; + playlistFrame = fManager->PlaylistFrameAtFrame(fFrame, + playingDirection, newPlayingState); + TRACE("_FrameGeneratorThread: performance time: %Ld, " + "playlist frame: %lld\n", performanceTime, playlistFrame); + forceSendingBuffer |= newPlayingState; + if (lastPlaylistFrame != playlistFrame) { + forceSendingBuffer = true; + lastPlaylistFrame = playlistFrame; + } + fManager->SetCurrentVideoTime(nextPerformanceTime); + fManager->Unlock(); + break; + } + case B_TIMED_OUT: + TRACE("_FrameGeneratorThread: Couldn't lock the node " + "manager.\n"); + ignoreEvent = true; + waitUntil = system_time() - 1; + break; + default: + ERROR("_FrameGeneratorThread: Couldn't lock the node manager. " + "Terminating video producer frame generator thread.\n"); + TRACE("_FrameGeneratorThread: frame generator thread done.\n"); + // do not access any member variables, since this could + // also me the Node has been deleted + return B_OK; + } + + TRACE("_FrameGeneratorThread: waiting (%Ld)...\n", waitUntil); + // wait until... + err = acquire_sem_etc(fFrameSync, 1, B_ABSOLUTE_TIMEOUT, waitUntil); + // The only acceptable responses are B_OK and B_TIMED_OUT. Everything + // else means the thread should quit. Deleting the semaphore, as in + // VideoProducer::_HandleStop(), will trigger this behavior. + switch (err) { + case B_OK: + TRACE("_FrameGeneratorThread: going back to sleep.\n"); + break; + case B_TIMED_OUT: + TRACE("_FrameGeneratorThread: timed out => event\n"); + // Catch the cases in which the node manager could not be + // locked and we therefore have no valid data to work with, + // or the producer is not running or enabled. + if (ignoreEvent || !fRunning || !fEnabled) { + TRACE("_FrameGeneratorThread: ignore event\n"); + // nothing to do + } else if (nextWaitUntil < system_time()) { + // Drop frame if it's at least a frame late. + //printf("VideoProducer: dropped frame (%ld)\n", fFrame); + if (fManager->LockWithTimeout(10000) == B_OK) { + fManager->FrameDropped(); + fManager->Unlock(); + } + // next frame + fFrame++; + } else if (playingDirection != 0 || forceSendingBuffer) { + // Send buffers only, if playing, the node is running and + // the output has been enabled + TRACE("_FrameGeneratorThread: produce frame\n"); + BAutolock _(fLock); + // Fetch a buffer from the buffer group + BBuffer *buffer = fUsedBufferGroup->RequestBuffer( + fConnectedFormat.display.bytes_per_row + * fConnectedFormat.display.line_count, 0LL); + if (buffer) { + // Fill out the details about this buffer. + media_header *h = buffer->Header(); + h->type = B_MEDIA_RAW_VIDEO; + h->time_source = TimeSource()->ID(); + h->size_used = fConnectedFormat.display.bytes_per_row + * fConnectedFormat.display.line_count; + // For a buffer originating from a device, you might + // want to calculate this based on the + // PerformanceTimeFor the time your buffer arrived at + // the hardware (plus any applicable adjustments). + h->start_time = fPerformanceTimeBase + performanceTime; +// TODO: Fix the runmode stuff! Setting the consumer to B_OFFLINE does +// not do the trick. I made the VideoConsumer check the performance +// time of the buffer and if it is 0, it plays it regardless. +if (playingMode < 0) { +h->start_time = 0; +} + h->file_pos = 0; + h->orig_size = 0; + h->data_offset = 0; + h->u.raw_video.field_gamma = 1.0; + h->u.raw_video.field_sequence = fFrame; + h->u.raw_video.field_number = 0; + h->u.raw_video.pulldown_number = 0; + h->u.raw_video.first_active_line = 1; + h->u.raw_video.line_count + = fConnectedFormat.display.line_count; + // Fill in a frame + media_format mf; + mf.type = B_MEDIA_RAW_VIDEO; + mf.u.raw_video = fConnectedFormat; + TRACE("_FrameGeneratorThread: frame: %Ld, " + "playlistFrame: %Ld\n", fFrame, playlistFrame); + bool forceOrWasCached = forceSendingBuffer; + + if (fManager->LockWithTimeout(5000) == B_OK) { + // we need to lock the manager, or our + // fSupplier might work on bad data + err = fSupplier->FillBuffer(playlistFrame, + buffer->Data(), &mf, forceOrWasCached); + fManager->Unlock(); + } else { + err = B_ERROR; + } + // clean the buffer if something went wrong + if (err != B_OK) { + memset(buffer->Data(), 0, h->size_used); + err = B_OK; + } + // Send the buffer on down to the consumer + if (SendBuffer(buffer, fOutput.destination) < B_OK) { + ERROR("_FrameGeneratorThread: Error " + "sending buffer\n"); + // If there is a problem sending the buffer, + // or if we don't send the buffer because its + // contents are the same as the last one, + // return it to its buffer group. + buffer->Recycle(); + // we tell the supplier to delete + // its caches if there was a problem sending + // the buffer + fSupplier->DeleteCaches(); + } + // Only if everything went fine we clear the flag + // that forces us to send a buffer even if not + // playing. + if (err == B_OK) { + forceSendingBuffer = false; + lastFrameSentAt = performanceTime; + } + } else { + TRACE("_FrameGeneratorThread: no buffer!\n"); +// ERROR("_FrameGeneratorThread: no buffer!\n"); + } + // next frame + fFrame++; + } else { + TRACE("_FrameGeneratorThread: not playing\n"); + // next frame + fFrame++; + } + break; + default: + TRACE("_FrameGeneratorThread: Couldn't acquire semaphore. " + "Error: %s\n", strerror(err)); + running = false; + break; + } + } + TRACE("_FrameGeneratorThread: frame generator thread done.\n"); + return B_OK; +} + diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h b/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h new file mode 100644 index 0000000000..72ea71b7ae --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoProducer.h @@ -0,0 +1,145 @@ +/* Copyright (c) 1998-99, Be Incorporated, All Rights Reserved. + * Distributed under the terms of the Be Sample Code license. + * + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef _VIDEO_PRODUCER_H +#define _VIDEO_PRODUCER_H + + +#include +#include +#include +#include +#include +#include +#include +#include + + +class NodeManager; +class VideoSupplier; + + +class VideoProducer : public virtual BMediaEventLooper, + public virtual BBufferProducer { +public: + VideoProducer(BMediaAddOn* addon, const char* name, + int32 internalId, NodeManager* manager, + VideoSupplier* supplier); + virtual ~VideoProducer(); + + virtual status_t InitCheck() const + { return fInitStatus; } + + // BMediaNode interface +public: + virtual port_id ControlPort() const; + virtual BMediaAddOn* AddOn(int32* _internalId) const; + virtual status_t HandleMessage(int32 message, const void* data, + size_t size); +protected: + virtual void SetTimeSource(BTimeSource* timeSource); + virtual status_t RequestCompleted(const media_request_info& info); + + // BMediaEventLooper interface +protected: + virtual void NodeRegistered(); + virtual void Start(bigtime_t performanceTime); + virtual void Stop(bigtime_t performanceTime, bool immediate); + virtual void Seek(bigtime_t mediaTime, + bigtime_t performanceTime); + virtual void TimeWarp(bigtime_t atRealTime, + bigtime_t toPerformanceTime); + virtual status_t AddTimer(bigtime_t atPerformanceTime, + int32 cookie); + virtual void SetRunMode(run_mode mode); + virtual void HandleEvent(const media_timed_event* event, + bigtime_t lateness, + bool realTimeEvent = false); + virtual void CleanUpEvent(const media_timed_event* event); + virtual bigtime_t OfflineTime(); + virtual void ControlLoop(); + virtual status_t DeleteHook(BMediaNode* node); + + // BBufferProducer interface +protected: + virtual status_t FormatSuggestionRequested(media_type type, + int32 quality, media_format* format); + virtual status_t FormatProposal(const media_source &output, + media_format* format); + virtual status_t FormatChangeRequested(const media_source& source, + const media_destination& destination, + media_format* ioFormat, int32* _deprecated_); + virtual status_t GetNextOutput(int32* cookie, + media_output* outOutput); + virtual status_t DisposeOutputCookie(int32 cookie); + virtual status_t SetBufferGroup(const media_source& forSource, + BBufferGroup* group); + virtual status_t VideoClippingChanged(const media_source& forSource, + int16 numShorts, int16* clipData, + const media_video_display_info& display, + int32* _deprecated_); + virtual status_t GetLatency(bigtime_t* out_latency); + virtual status_t PrepareToConnect(const media_source& what, + const media_destination& where, + media_format* format, media_source* outSource, + char* out_name); + virtual void Connect(status_t error, const media_source& source, + const media_destination& destination, + const media_format& format, char* ioName); + virtual void Disconnect(const media_source& what, + const media_destination& where); + virtual void LateNoticeReceived(const media_source& what, + bigtime_t howMuch, bigtime_t performanceTime); + virtual void EnableOutput(const media_source& what, bool enabled, + int32* _deprecated_); + virtual status_t SetPlayRate(int32 numer, int32 denom); + virtual void AdditionalBufferRequested( + const media_source& source, + media_buffer_id prevBuffer, + bigtime_t prevTime, + const media_seek_tag* prevTag); + virtual void LatencyChanged(const media_source& source, + const media_destination& destination, + bigtime_t newLatency, uint32 flags); + + private: + void _HandleStart(bigtime_t performance_time); + void _HandleStop(); + void _HandleTimeWarp(bigtime_t performance_time); + void _HandleSeek(bigtime_t performance_time); + + static int32 _FrameGeneratorThreadEntry(void* data); + int32 _FrameGeneratorThread(); + + status_t fInitStatus; + + int32 fInternalID; + BMediaAddOn* fAddOn; + + BLocker fLock; + BBufferGroup* fBufferGroup; + BBufferGroup* fUsedBufferGroup; + + thread_id fThread; + sem_id fFrameSync; + + // The remaining variables should be declared volatile, but they + // are not here to improve the legibility of the sample code. + int64 fFrame; + int64 fFrameBase; + bigtime_t fPerformanceTimeBase; + media_output fOutput; + media_raw_video_format fConnectedFormat; + bool fRunning; + bool fConnected; + bool fEnabled; + + NodeManager* fManager; + VideoSupplier* fSupplier; +}; + +#endif // VIDEO_PRODUCER_H diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoSupplier.cpp b/src/apps/mediaplayer/media_node_framework/video/VideoSupplier.cpp new file mode 100644 index 0000000000..5810a99863 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoSupplier.cpp @@ -0,0 +1,23 @@ +/* + * Copyright 2001-2008 Ingo Weinhold + * Copyright 2001-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#include "VideoSupplier.h" + + +VideoSupplier::VideoSupplier() + : fProcessingLatency(1000) +{ +} + + +VideoSupplier::~VideoSupplier() +{ +} + + +void +VideoSupplier::DeleteCaches() +{ +} diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoSupplier.h b/src/apps/mediaplayer/media_node_framework/video/VideoSupplier.h new file mode 100644 index 0000000000..9a895580ea --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoSupplier.h @@ -0,0 +1,34 @@ +/* + * Copyright 2001-2008 Ingo Weinhold + * Copyright 2001-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#ifndef VIDEO_SUPPLIER_H +#define VIDEO_SUPPLIER_H + + +#include + + +struct media_format; + + +class VideoSupplier { + public: + VideoSupplier(); + virtual ~VideoSupplier(); + + virtual status_t FillBuffer(int64 startFrame, void* buffer, + const media_format* format, + bool& wasCached) = 0; + + virtual void DeleteCaches(); + + inline bigtime_t ProcessingLatency() const + { return fProcessingLatency; } + + protected: + bigtime_t fProcessingLatency; +}; + +#endif // VIDEO_SUPPLIER_H diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoTarget.cpp b/src/apps/mediaplayer/media_node_framework/video/VideoTarget.cpp new file mode 100644 index 0000000000..454ace520f --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoTarget.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2000-2008 Ingo Weinhold All rights reserved. + * Distributed under the terms of the MIT license. + */ +#include "VideoTarget.h" + + +VideoTarget::VideoTarget() + : fBitmapLock(), + fBitmap(NULL) +{ +} + + +VideoTarget::~VideoTarget() +{ +} + + +bool +VideoTarget::LockBitmap() +{ + return fBitmapLock.Lock(); +} + + +void +VideoTarget::UnlockBitmap() +{ + fBitmapLock.Unlock(); +} + + +void +VideoTarget::SetBitmap(const BBitmap* bitmap) +{ + LockBitmap(); + fBitmap = bitmap; + UnlockBitmap(); +} + + +const BBitmap* +VideoTarget::GetBitmap() const +{ + return fBitmap; +} + diff --git a/src/apps/mediaplayer/media_node_framework/video/VideoTarget.h b/src/apps/mediaplayer/media_node_framework/video/VideoTarget.h new file mode 100644 index 0000000000..4adc4d9b57 --- /dev/null +++ b/src/apps/mediaplayer/media_node_framework/video/VideoTarget.h @@ -0,0 +1,40 @@ +/* + * Copyright 2000-2008 Ingo Weinhold All rights reserved. + * Distributed under the terms of the MIT license. + */ + +/*! Derived classes are video consumer targets. Each time the consumer + has received a frame (that is not late and thus dropped) it calls + SetBitmap(). This method should immediately do whatever has to be done. + Until the next call to SetBitmap() the bitmap can be used -- thereafter + it is not allowed to use it any longer. Therefore the bitmap variable + is protected by a lock. Anytime it is going to be accessed, the object + must be locked. +*/ +#ifndef VIDEO_TARGET_H +#define VIDEO_TARGET_H + + +#include + + +class BBitmap; + + +class VideoTarget { + public: + VideoTarget(); + virtual ~VideoTarget(); + + bool LockBitmap(); + void UnlockBitmap(); + + virtual void SetBitmap(const BBitmap* bitmap); + const BBitmap* GetBitmap() const; + + protected: + BLocker fBitmapLock; + const BBitmap* volatile fBitmap; +}; + +#endif // VIDEO_TARGET_H diff --git a/src/apps/mediaplayer/supplier/AudioSupplier.h b/src/apps/mediaplayer/supplier/AudioSupplier.h deleted file mode 100644 index 6b5e512a73..0000000000 --- a/src/apps/mediaplayer/supplier/AudioSupplier.h +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2007, Haiku. All rights reserved. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus - */ -#ifndef AUDIO_SUPPLIER_H -#define AUDIO_SUPPLIER_H - - -#include -#include - -class AudioSupplier { - public: - AudioSupplier(); - virtual ~AudioSupplier(); - - virtual media_format Format() const = 0; - virtual status_t GetEncodedFormat(media_format* format) - const = 0; - virtual status_t GetCodecInfo(media_codec_info* info) const = 0; - - virtual status_t ReadFrames(void* buffer, int64* framesRead, - bigtime_t* performanceTime) = 0; - virtual status_t SeekToTime(bigtime_t* performanceTime) = 0; - - virtual bigtime_t Position() const = 0; - virtual bigtime_t Duration() const = 0; -}; - -#endif // AUDIO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/AudioSupplier.cpp b/src/apps/mediaplayer/supplier/AudioTrackSupplier.cpp similarity index 56% rename from src/apps/mediaplayer/supplier/AudioSupplier.cpp rename to src/apps/mediaplayer/supplier/AudioTrackSupplier.cpp index 7fdd149bee..1d010d2370 100644 --- a/src/apps/mediaplayer/supplier/AudioSupplier.cpp +++ b/src/apps/mediaplayer/supplier/AudioTrackSupplier.cpp @@ -5,15 +5,16 @@ * Authors: * Stephan Aßmus */ -#include "AudioSupplier.h" +#include "AudioTrackSupplier.h" -AudioSupplier::AudioSupplier() +AudioTrackSupplier::AudioTrackSupplier() + : AudioReader() { } -AudioSupplier::~AudioSupplier() +AudioTrackSupplier::~AudioTrackSupplier() { } diff --git a/src/apps/mediaplayer/supplier/AudioTrackSupplier.h b/src/apps/mediaplayer/supplier/AudioTrackSupplier.h new file mode 100644 index 0000000000..0e5001ac3c --- /dev/null +++ b/src/apps/mediaplayer/supplier/AudioTrackSupplier.h @@ -0,0 +1,28 @@ +/* + * Copyright 2007-2008, Haiku. All rights reserved. + * Distributed under the terms of the MIT License. + * + * Authors: + * Stephan Aßmus + */ +#ifndef AUDIO_TRACK_SUPPLIER_H +#define AUDIO_TRACK_SUPPLIER_H + +#include +#include + +#include "AudioReader.h" + +class AudioTrackSupplier : public AudioReader { + public: + AudioTrackSupplier(); + virtual ~AudioTrackSupplier(); + + virtual const media_format& Format() const = 0; + virtual status_t GetEncodedFormat(media_format* format) + const = 0; + virtual status_t GetCodecInfo(media_codec_info* info) const = 0; + virtual bigtime_t Duration() const = 0; +}; + +#endif // AUDIO_TRACK_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp index 879e8fac24..77b93c1cc6 100644 --- a/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp +++ b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.cpp @@ -1,64 +1,85 @@ /* - * Copyright 2007, Haiku. All rights reserved. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus + * Copyright © 2000-2004 Ingo Weinhold + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT License. */ #include "MediaTrackAudioSupplier.h" #include +#include #include #include +#include #include using std::nothrow; -// constructor -MediaTrackAudioSupplier::MediaTrackAudioSupplier(BMediaTrack* track) - : AudioSupplier() - , fMediaTrack(track) - , fPerformanceTime(0) - , fDuration(0) +//#define TRACE_AUDIO_SUPPLIER +#ifdef TRACE_AUDIO_SUPPLIER +# define TRACE(x...) printf("MediaTrackAudioSupplier::"); printf(x) +#else +# define TRACE(x...) +#endif + + +// #pragma mark - Buffer + + +struct MediaTrackAudioSupplier::Buffer { + void* data; + int64 offset; + int64 size; + bigtime_t time_stamp; + + static int CompareOffset(const void* a, const void* b); +}; + + +int +MediaTrackAudioSupplier::Buffer::CompareOffset(const void* a, const void* b) { - if (!fMediaTrack) { - printf("MediaTrackAudioSupplier() - no media track\n"); - return; - } - - fFormat.u.raw_audio = media_multi_audio_format::wildcard; - #ifdef __HAIKU__ - fFormat.u.raw_audio.format = media_multi_audio_format::B_AUDIO_FLOAT; - #endif - status_t ret = fMediaTrack->DecodedFormat(&fFormat); - if (ret < B_OK) { - printf("MediaTrackAudioSupplier() - " - "fMediaTrack->DecodedFormat(): %s\n", strerror(ret)); - return; - } - - fDuration = fMediaTrack->Duration(); -// -// for (bigtime_t time = 0; time < fDuration; time += 10000) { -// bigtime_t keyFrameTime = time; -// fMediaTrack->FindKeyFrameForTime(&keyFrameTime, -// B_MEDIA_SEEK_CLOSEST_BACKWARD); -// printf("audio keyframe time for time: %lld -> %lld\n", time, keyFrameTime); -// } + const Buffer* buffer1 = *(const Buffer**)a; + const Buffer* buffer2 = *(const Buffer**)b; + int result = 0; + if (buffer1->offset < buffer2->offset) + result = -1; + else if (buffer1->offset > buffer2->offset) + result = 1; + return result; } -// destructor + +// #pragma mark - MediaTrackAudioSupplier + + +MediaTrackAudioSupplier::MediaTrackAudioSupplier(BMediaTrack* mediaTrack) + : AudioTrackSupplier(), + fMediaTrack(mediaTrack), + fBuffer(NULL), + fBufferOffset(0), + fBufferSize(0), + fBuffers(10), + fHasKeyFrames(false), + fCountFrames(0), + fReportSeekError(true) +{ + _InitFromTrack(); +} + + MediaTrackAudioSupplier::~MediaTrackAudioSupplier() { + _FreeBuffers(); + delete[] fBuffer; } -media_format +const media_format& MediaTrackAudioSupplier::Format() const { - return fFormat; + return AudioReader::Format(); } @@ -80,53 +101,530 @@ MediaTrackAudioSupplier::GetCodecInfo(media_codec_info* info) const } -status_t -MediaTrackAudioSupplier::ReadFrames(void* buffer, int64* framesRead, - bigtime_t* performanceTime) +bigtime_t +MediaTrackAudioSupplier::Duration() const { if (!fMediaTrack) - return B_NO_INIT; - if (!buffer || !framesRead) - return B_BAD_VALUE; + return 0; - media_header mediaHeader; - status_t ret = fMediaTrack->ReadFrames(buffer, framesRead, &mediaHeader); + return fMediaTrack->Duration(); +} - if (ret < B_OK) { - // further analyse the error - if (fDuration == 0 || (double)fPerformanceTime / fDuration > 0.95) { - // some codecs don't behave well, or maybe somes files are bad, - // they don't report the end of the stream correctly - // NOTE: "more than 95% of the stream" is of course pure guess, - // but it fixed the problem I had with some files - ret = B_LAST_BUFFER_ERROR; + +// #pragma mark - AudioReader + + +// Read +status_t +MediaTrackAudioSupplier::Read(void* buffer, int64 pos, int64 frames) +{ + TRACE("Read(%p, %lld, %lld)\n", buffer, pos, + frames); + TRACE(" this: %p, fOutOffset: %lld\n", this, fOutOffset); + + status_t error = InitCheck(); + if (error != B_OK) { + TRACE("Read() done\n"); + return error; + } + + // convert pos according to our offset + pos += fOutOffset; + // Fill the frames after the end of the track with silence. + if (pos + frames > fCountFrames) { + int64 size = max(0LL, fCountFrames - pos); + ReadSilence(SkipFrames(buffer, size), frames - size); + frames = size; + } + + TRACE(" after eliminating the frames after the track end: %p, %lld, %lld\n", + buffer, pos, frames); + + // read the cached frames + bigtime_t time = system_time(); + if (frames > 0) + _ReadCachedFrames(buffer, pos, frames, time); + + TRACE(" after reading from cache: %p, %lld, %lld\n", buffer, pos, frames); + + // read the remaining (uncached) frames + if (frames > 0) + _ReadUncachedFrames(buffer, pos, frames, time); + + TRACE("Read() done\n"); + + return B_OK; +} + +// InitCheck +status_t +MediaTrackAudioSupplier::InitCheck() const +{ + status_t error = AudioReader::InitCheck(); + if (error == B_OK && (!fMediaTrack || !fBuffer)) + error = B_NO_INIT; + return error; +} + +// #pragma mark - + +// _InitFromTrack +void +MediaTrackAudioSupplier::_InitFromTrack() +{ + if (fMediaTrack && fMediaTrack->DecodedFormat(&fFormat) == B_OK + && fFormat.type == B_MEDIA_RAW_AUDIO) { + + #ifdef TRACE_AUDIO_SUPPLIER + char formatString[256]; + string_for_format(fFormat, formatString, 256); + TRACE("MediaTrackAudioSupplier: format is: %s\n", formatString); + #endif + + fBuffer = new (nothrow) char[fFormat.u.raw_audio.buffer_size]; + _AllocateBuffers(); + + // Find out, if the track has key frames: as a heuristic we + // check, if the first and the second frame have the same backward + // key frame. + // Note: It shouldn't harm that much, if we're wrong and the + // track has key frame although we found out that it has not. + int64 keyFrame0 = 0; + int64 keyFrame1 = 1; + fMediaTrack->FindKeyFrameForFrame(&keyFrame0, + B_MEDIA_SEEK_CLOSEST_BACKWARD); + fMediaTrack->FindKeyFrameForFrame(&keyFrame1, + B_MEDIA_SEEK_CLOSEST_BACKWARD); + fHasKeyFrames = (keyFrame0 == keyFrame1); + + // get the length of the track + fCountFrames = fMediaTrack->CountFrames(); + } else + fMediaTrack = NULL; +} + +// _FramesPerBuffer +int64 +MediaTrackAudioSupplier::_FramesPerBuffer() const +{ + int64 sampleSize = fFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int64 frameSize = sampleSize * fFormat.u.raw_audio.channel_count; + return fFormat.u.raw_audio.buffer_size / frameSize; +} + +// _CopyFrames +// +// Given two buffers starting at different frame offsets, this function +// copies /frames/ frames at position /position/ from the source to the +// target buffer. +// Note that no range checking is done. +void +MediaTrackAudioSupplier::_CopyFrames(void* source, int64 sourceOffset, + void* target, int64 targetOffset, + int64 position, int64 frames) const +{ + int64 sampleSize = fFormat.u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int64 frameSize = sampleSize * fFormat.u.raw_audio.channel_count; + source = (char*)source + frameSize * (position - sourceOffset); + target = (char*)target + frameSize * (position - targetOffset); + memcpy(target, source, frames * frameSize); +} + +// _CopyFrames +// +// Given two buffers starting at different frame offsets, this function +// copies /frames/ frames at position /position/ from the source to the +// target buffer. This version expects a cache buffer as source. +// Note that no range checking is done. +void +MediaTrackAudioSupplier::_CopyFrames(Buffer* buffer, + void* target, int64 targetOffset, + int64 position, int64 frames) const +{ + _CopyFrames(buffer->data, buffer->offset, target, targetOffset, position, + frames); +} + +// _AllocateBuffers +// +// Allocates a set of buffers. +void +MediaTrackAudioSupplier::_AllocateBuffers() +{ + int32 count = 10; + _FreeBuffers(); + int32 bufferSize = fFormat.u.raw_audio.buffer_size; + char* data = new (nothrow) char[bufferSize * count]; + for (; count > 0; count--) { + Buffer* buffer = new (nothrow) Buffer; + if (!buffer || !fBuffers.AddItem(buffer)) { + delete buffer; + return; } - printf("MediaTrackAudioSupplier::ReadFrame() - " - "error while reading frames: %s\n", strerror(ret)); - } else { - fPerformanceTime = mediaHeader.start_time; + buffer->data = data; + data += bufferSize; + buffer->offset = 0; + buffer->size = 0; + buffer->time_stamp = 0; } - - if (performanceTime) - *performanceTime = fPerformanceTime; - - return ret; } - -status_t -MediaTrackAudioSupplier::SeekToTime(bigtime_t* performanceTime) +// _FreeBuffers +// +// Frees the allocated buffers. +void +MediaTrackAudioSupplier::_FreeBuffers() { - if (!fMediaTrack) - return B_NO_INIT; - -bigtime_t _performanceTime = *performanceTime; - status_t ret = fMediaTrack->SeekToTime(performanceTime); - if (ret == B_OK) { -printf("seeked: %lld -> %lld\n", _performanceTime, *performanceTime); - fPerformanceTime = *performanceTime; + if (fBuffers.CountItems() > 0) { + delete[] (char*)_BufferAt(0)->data; + for (int32 i = 0; Buffer* buffer = _BufferAt(i); i++) + delete buffer; + fBuffers.MakeEmpty(); } - - return ret; +} + +// _BufferAt +// +// Returns the buffer at index /index/. +MediaTrackAudioSupplier::Buffer* +MediaTrackAudioSupplier::_BufferAt(int32 index) const +{ + return (Buffer*)fBuffers.ItemAt(index); +} + +// _FindBufferAtFrame +// +// If any buffer starts at offset /frame/, it is returned, NULL otherwise. +MediaTrackAudioSupplier::Buffer* +MediaTrackAudioSupplier::_FindBufferAtFrame(int64 frame) const +{ + Buffer* buffer = NULL; + for (int32 i = 0; + ((buffer = _BufferAt(i))) && buffer->offset != frame; + i++); + return buffer; +} + +// _FindUnusedBuffer +// +// Returns the first unused buffer or NULL if all buffers are used. +MediaTrackAudioSupplier::Buffer* +MediaTrackAudioSupplier::_FindUnusedBuffer() const +{ + Buffer* buffer = NULL; + for (int32 i = 0; ((buffer = _BufferAt(i))) && buffer->size != 0; i++); + return buffer; +} + +// _FindUsableBuffer +// +// Returns either an unused buffer or, if all buffers are used, the least +// recently used buffer. +// In every case a buffer is returned. +MediaTrackAudioSupplier::Buffer* +MediaTrackAudioSupplier::_FindUsableBuffer() const +{ + Buffer* result = _FindUnusedBuffer(); + if (!result) { + // find the least recently used buffer. + result = _BufferAt(0); + for (int32 i = 1; Buffer* buffer = _BufferAt(i); i++) { + if (buffer->time_stamp < result->time_stamp) + result = buffer; + } + } + return result; +} + +// _FindUsableBufferFor +// +// In case there already exists a buffer that starts at position this +// one is returned. Otherwise the function returns either an unused +// buffer or, if all buffers are used, the least recently used buffer. +// In every case a buffer is returned. +MediaTrackAudioSupplier::Buffer* +MediaTrackAudioSupplier::_FindUsableBufferFor(int64 position) const +{ + Buffer* buffer = _FindBufferAtFrame(position); + if (!buffer) + buffer = _FindUsableBuffer(); + return buffer; +} + +// _GetBuffersFor +// +// Adds pointers to all buffers to the list that contain data of the +// supplied interval. +void +MediaTrackAudioSupplier::_GetBuffersFor(BList& buffers, int64 position, + int64 frames) const +{ + buffers.MakeEmpty(); + for (int32 i = 0; Buffer* buffer = _BufferAt(i); i++) { + // Calculate the intersecting interval and add the buffer if it is + // not empty. + int32 startFrame = max(position, buffer->offset); + int32 endFrame = min(position + frames, buffer->offset + buffer->size); + if (startFrame < endFrame) + buffers.AddItem(buffer); + } +} + +// _TouchBuffer +// +// Sets a buffer's time stamp to the current system time. +void +MediaTrackAudioSupplier::_TouchBuffer(Buffer* buffer) +{ + buffer->time_stamp = system_time(); +} + +// _ReadBuffer +// +// Read a buffer from the current position (which is supplied in /position/) +// into /buffer/. The buffer's time stamp is set to the current system time. +status_t +MediaTrackAudioSupplier::_ReadBuffer(Buffer* buffer, int64 position) +{ + return _ReadBuffer(buffer, position, system_time()); +} + +// _ReadBuffer +// +// Read a buffer from the current position (which is supplied in /position/) +// into /buffer/. The buffer's time stamp is set to the supplied time. +status_t +MediaTrackAudioSupplier::_ReadBuffer(Buffer* buffer, int64 position, + bigtime_t time) +{ + status_t error = fMediaTrack->ReadFrames(buffer->data, &buffer->size); + + TRACE("Read(%p, %lld): %s\n", buffer->data, buffer->size, strerror(error)); + + buffer->offset = position; + buffer->time_stamp = time; + if (error != B_OK) + buffer->size = 0; + return error; +} + +// _ReadCachedFrames +// +// Tries to read as much as possible data from the cache. The supplied +// buffer pointer as well as position and number of frames are adjusted +// accordingly. The used cache buffers are stamped with the current +// system time. +void +MediaTrackAudioSupplier::_ReadCachedFrames(void*& dest, int64& pos, + int64& frames) +{ + _ReadCachedFrames(dest, pos, frames, system_time()); +} + +// _ReadCachedFrames +// +// Tries to read as much as possible data from the cache. The supplied +// buffer pointer as well as position and number of frames are adjusted +// accordingly. The used cache buffers are stamped with the supplied +// time. +void +MediaTrackAudioSupplier::_ReadCachedFrames(void*& dest, int64& pos, + int64& frames, bigtime_t time) +{ + // Get a list of all cache buffers that contain data of the interval, + // and sort it. + BList buffers(10); + _GetBuffersFor(buffers, pos, frames); + buffers.SortItems(Buffer::CompareOffset); + // Step forward through the list of cache buffers and try to read as + // much data from the beginning as possible. + for (int32 i = 0; Buffer* buffer = (Buffer*)buffers.ItemAt(i); i++) { + if (buffer->offset <= pos && buffer->offset + buffer->size > pos) { + // read from the beginning + int64 size = min(frames, buffer->offset + buffer->size - pos); + _CopyFrames(buffer->data, buffer->offset, dest, pos, pos, size); + pos += size; + frames -= size; + dest = SkipFrames(dest, size); + } + buffer->time_stamp = time; + } + // Step backward through the list of cache buffers and try to read as + // much data from the end as possible. + for (int32 i = buffers.CountItems() - 1; + Buffer* buffer = (Buffer*)buffers.ItemAt(i); + i++) { + if (buffer->offset < pos + frames + && buffer->offset + buffer->size >= pos + frames) { + // read from the end + int64 size = min(frames, pos + frames - buffer->offset); + _CopyFrames(buffer->data, buffer->offset, dest, pos, + pos + frames - size, size); + frames -= size; + } + } +} + +// _ReadUncachedFrames +// +// Reads /frames/ frames from /position/ into /buffer/. The frames are not +// read from the cache, but read frames are cached, if possible. +// New cache buffers are stamped with the system time. +// If an error occurs, the untouched part of the buffer is set to 0. +status_t +MediaTrackAudioSupplier::_ReadUncachedFrames(void* buffer, int64 position, + int64 frames) +{ + return _ReadUncachedFrames(buffer, position, frames, system_time()); +} + +// _ReadUncachedFrames +// +// Reads /frames/ frames from /position/ into /buffer/. The frames are not +// read from the cache, but read frames are cached, if possible. +// New cache buffers are stamped with the supplied time. +// If an error occurs, the untouched part of the buffer is set to 0. +status_t +MediaTrackAudioSupplier::_ReadUncachedFrames(void* buffer, int64 position, + int64 frames, bigtime_t time) +{ + status_t error = B_OK; + // seek to the position + int64 currentPos = position; + if (frames > 0) { + error = _SeekToKeyFrameBackward(currentPos); + TRACE("_ReadUncachedFrames() - seeked to position: %lld\n", currentPos); + } + // read the frames + while (error == B_OK && frames > 0) { + Buffer* cacheBuffer = _FindUsableBufferFor(currentPos); + TRACE("_ReadUncachedFrames() - usable buffer found: %p\n", cacheBuffer); + error = _ReadBuffer(cacheBuffer, currentPos, time); + if (error == B_OK) { + int64 size = min(position + frames, + cacheBuffer->offset + cacheBuffer->size) + - position; + if (size > 0) { + _CopyFrames(cacheBuffer, buffer, position, position, size); + buffer = SkipFrames(buffer, size); + position += size; + frames -= size; + } + currentPos += cacheBuffer->size; + } + } + // Ensure that all frames up to the next key frame are cached. + // This avoids, that each read + if (error == B_OK) { + int64 nextKeyFrame = currentPos; + if (_FindKeyFrameForward(nextKeyFrame) == B_OK) { + while (currentPos < nextKeyFrame) { + // Check, if data at this position are cache. + // If not read it. + Buffer* cacheBuffer = _FindBufferAtFrame(currentPos); + if (!cacheBuffer || cacheBuffer->size == 0) { + cacheBuffer = _FindUsableBufferFor(currentPos); + if (_ReadBuffer(cacheBuffer, currentPos, time) != B_OK) + break; + } + if (cacheBuffer) + currentPos += cacheBuffer->size; + } + } + } + // on error fill up the buffer with silence + if (error != B_OK && frames > 0) + ReadSilence(buffer, frames); + return error; +} + +// _FindKeyFrameForward +status_t +MediaTrackAudioSupplier::_FindKeyFrameForward(int64& position) +{ + status_t error = B_OK; +// NOTE: the keyframe version confuses the Frauenhofer MP3 decoder, +// it works fine with the non-keyframe version, so let's hope this +// is the case for all other keyframe based BeOS codecs... +// if (fHasKeyFrames) { +// error = fMediaTrack->FindKeyFrameForFrame( +// &position, B_MEDIA_SEEK_CLOSEST_FORWARD); +// } else { + int64 framesPerBuffer = _FramesPerBuffer(); + position += framesPerBuffer - 1; + position = position % framesPerBuffer; +// } + return error; +} + +// _FindKeyFrameBackward +status_t +MediaTrackAudioSupplier::_FindKeyFrameBackward(int64& position) +{ + status_t error = B_OK; + if (fHasKeyFrames) { + error = fMediaTrack->FindKeyFrameForFrame( + &position, B_MEDIA_SEEK_CLOSEST_BACKWARD); + } else + position -= position % _FramesPerBuffer(); + return error; +} + +// _SeekToKeyFrameForward +status_t +MediaTrackAudioSupplier::_SeekToKeyFrameForward(int64& position) +{ + if (position == fMediaTrack->CurrentFrame()) + return B_OK; + + status_t error = B_OK; + if (fHasKeyFrames) { + #ifdef TRACE_AUDIO_SUPPLIER + int64 oldPosition = position; + #endif + error = fMediaTrack->SeekToFrame(&position, + B_MEDIA_SEEK_CLOSEST_FORWARD); + TRACE("_SeekToKeyFrameForward() - seek to key frame forward: " + "%lld -> %lld (%lld)\n", oldPosition, position, + fMediaTrack->CurrentFrame()); + } else { + _FindKeyFrameForward(position); + error = fMediaTrack->SeekToFrame(&position); + } + return error; +} + +// _SeekToKeyFrameBackward +status_t +MediaTrackAudioSupplier::_SeekToKeyFrameBackward(int64& position) +{ + if (position == fMediaTrack->CurrentFrame()) + return B_OK; + + status_t error = B_OK; + if (fHasKeyFrames) { + int64 oldPosition = position; + error = fMediaTrack->FindKeyFrameForFrame(&position, + B_MEDIA_SEEK_CLOSEST_BACKWARD); + if (error >= B_OK) + error = fMediaTrack->SeekToFrame(&position, 0); + if (error < B_OK) { + position = fMediaTrack->CurrentFrame(); + if (fReportSeekError) { + printf(" seek to key frame backward: %lld -> %lld (%lld) " + "- %s\n", oldPosition, position, + fMediaTrack->CurrentFrame(), strerror(error)); + fReportSeekError = false; + } + } else { + fReportSeekError = true; + } + } else { + _FindKeyFrameBackward(position); + error = fMediaTrack->SeekToFrame(&position); + } + return error; } diff --git a/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h index 0676d7734a..51c2fa9494 100644 --- a/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h +++ b/src/apps/mediaplayer/supplier/MediaTrackAudioSupplier.h @@ -1,41 +1,89 @@ /* - * Copyright 2007, Haiku. All rights reserved. - * Distributed under the terms of the MIT License. - * - * Authors: - * Stephan Aßmus + * Copyright © 2000-2004 Ingo Weinhold + * Copyright © 2006-2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT License. */ #ifndef MEDIA_TRACK_AUDIO_SUPPLIER_H #define MEDIA_TRACK_AUDIO_SUPPLIER_H -#include "AudioSupplier.h" +#include + +#include "AudioTrackSupplier.h" class BMediaTrack; +struct media_codec_info; +struct media_format; -class MediaTrackAudioSupplier : public AudioSupplier { +class MediaTrackAudioSupplier : public AudioTrackSupplier { public: MediaTrackAudioSupplier(BMediaTrack* track); virtual ~MediaTrackAudioSupplier(); - virtual media_format Format() const; + virtual const media_format& Format() const; virtual status_t GetEncodedFormat(media_format* format) const; virtual status_t GetCodecInfo(media_codec_info* info) const; + virtual bigtime_t Duration() const; - virtual status_t ReadFrames(void* buffer, int64* framesRead, - bigtime_t* performanceTime); - virtual status_t SeekToTime(bigtime_t* performanceTime); + // AudioReader interface + // (needed to reuse the class as AudioResampler input) + virtual status_t Read(void* buffer, int64 pos, int64 frames); + + virtual status_t InitCheck() const; + + private: + struct Buffer; + void _InitFromTrack(); + + int64 _FramesPerBuffer() const; + + void _CopyFrames(void* source, int64 sourceOffset, + void* target, int64 targetOffset, + int64 position, int64 frames) const; + void _CopyFrames(Buffer* buffer, void* target, + int64 targetOffset, int64 position, + int64 frames) const; + + void _AllocateBuffers(); + void _FreeBuffers(); + Buffer* _BufferAt(int32 index) const; + Buffer* _FindBufferAtFrame(int64 frame) const; + Buffer* _FindUnusedBuffer() const; + Buffer* _FindUsableBuffer() const; + Buffer* _FindUsableBufferFor(int64 position) const; + void _GetBuffersFor(BList& buffers, int64 position, + int64 frames) const; + void _TouchBuffer(Buffer* buffer); + + status_t _ReadBuffer(Buffer* buffer, int64 position); + status_t _ReadBuffer(Buffer* buffer, int64 position, + bigtime_t time); + + void _ReadCachedFrames(void*& buffer, + int64& position, int64& frames); + void _ReadCachedFrames(void*& buffer, + int64& position, int64& frames, + bigtime_t time); + + status_t _ReadUncachedFrames(void* buffer, + int64 position, int64 frames); + status_t _ReadUncachedFrames(void* buffer, + int64 position, int64 frames, + bigtime_t time); + + status_t _FindKeyFrameForward(int64& position); + status_t _FindKeyFrameBackward(int64& position); + status_t _SeekToKeyFrameForward(int64& position); + status_t _SeekToKeyFrameBackward(int64& position); - virtual bigtime_t Position() const - { return fPerformanceTime; } - virtual bigtime_t Duration() const - { return fDuration; } private: BMediaTrack* fMediaTrack; - - media_format fFormat; - - bigtime_t fPerformanceTime; - bigtime_t fDuration; + char* fBuffer; + int64 fBufferOffset; + int64 fBufferSize; + BList fBuffers; + bool fHasKeyFrames; + int64 fCountFrames; + bool fReportSeekError; }; -#endif // MEDIA_TRACK_AUDIO_SUPPLIER_H +#endif // MEDIA_TRACK_AUDIO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp index c3aaa7b1c0..ea0d44f045 100644 --- a/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp +++ b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.cpp @@ -29,73 +29,19 @@ static const char* string_for_color_space(color_space format); // constructor MediaTrackVideoSupplier::MediaTrackVideoSupplier(BMediaTrack* track, color_space format) - : VideoSupplier() + : VideoTrackSupplier() , fVideoTrack(track) , fPerformanceTime(0) , fDuration(0) + , fCurrentFrame(0) { if (!fVideoTrack) { printf("MediaTrackVideoSupplier() - no video track\n"); return; } - // get the encoded format - memset(&fFormat, 0, sizeof(media_format)); - status_t ret = fVideoTrack->EncodedFormat(&fFormat); - if (ret < B_OK) { - printf("MediaTrackVideoSupplier::InitCheck() - " - "fVideoTrack->EncodedFormat(): %s\n", strerror(ret)); - return; - } - - // get ouput video frame size - uint32 width = fFormat.u.encoded_video.output.display.line_width; - uint32 height = fFormat.u.encoded_video.output.display.line_count; - - // specifiy the decoded format. we derive this information from - // the encoded format (width & height). - memset(&fFormat, 0, sizeof(media_format)); -// fFormat.u.raw_video.last_active = height - 1; -// fFormat.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; -// fFormat.u.raw_video.pixel_width_aspect = 1; -// fFormat.u.raw_video.pixel_height_aspect = 1; - fFormat.u.raw_video.display.format = format; - fFormat.u.raw_video.display.line_width = width; - fFormat.u.raw_video.display.line_count = height; - if (format == B_RGB32 || format == B_RGBA32) - fFormat.u.raw_video.display.bytes_per_row = width * 4; - else if (format == B_YCbCr422) - fFormat.u.raw_video.display.bytes_per_row = ((width * 2 + 3) / 4) * 4; - - ret = fVideoTrack->DecodedFormat(&fFormat); - - if (ret < B_OK) { - printf("MediaTrackVideoSupplier() - " - "fVideoTrack->DecodedFormat(): %s\n", strerror(ret)); - return; - } - - if (fFormat.u.raw_video.display.format != format) { - printf("MediaTrackVideoSupplier() - " - " codec changed colorspace of decoded format (%s -> %s)!\n" - " this is bad for performance, since colorspace conversion\n" - " needs to happen during playback.\n", - string_for_color_space(format), - string_for_color_space(fFormat.u.raw_video.display.format)); - // check if the codec forgot to adjust bytes_per_row - uint32 minBPR; - format = fFormat.u.raw_video.display.format; - if (format == B_YCbCr422) - minBPR = ((width * 2 + 3) / 4) * 4; - else - minBPR = width * 4; - if (minBPR != fFormat.u.raw_video.display.bytes_per_row) { - printf(" -> stupid codec forgot to adjust bytes_per_row!\n"); - fFormat.u.raw_video.display.bytes_per_row = minBPR; - fVideoTrack->DecodedFormat(&fFormat); - } - } + _SwitchFormat(format, 0); fDuration = fVideoTrack->Duration(); @@ -113,7 +59,7 @@ MediaTrackVideoSupplier::~MediaTrackVideoSupplier() } -media_format +const media_format& MediaTrackVideoSupplier::Format() const { return fFormat; @@ -139,26 +85,44 @@ MediaTrackVideoSupplier::GetCodecInfo(media_codec_info* info) const status_t -MediaTrackVideoSupplier::ReadFrame(void* buffer, bigtime_t* performanceTime) +MediaTrackVideoSupplier::ReadFrame(void* buffer, bigtime_t* performanceTime, + const media_format* format, bool& wasCached) { if (!fVideoTrack) return B_NO_INIT; if (!buffer) return B_BAD_VALUE; + status_t ret = B_OK; + if (format->u.raw_video.display.format + != fFormat.u.raw_video.display.format + || fFormat.u.raw_video.display.bytes_per_row + != format->u.raw_video.display.bytes_per_row) { + ret = _SwitchFormat(format->u.raw_video.display.format, + format->u.raw_video.display.bytes_per_row); + if (ret < B_OK) { + fprintf(stderr, "MediaTrackVideoSupplier::ReadFrame() - " + "unable to switch media format: %s\n", strerror(ret)); + return ret; + } + } + // read a frame int64 frameCount = 1; // TODO: how does this work for interlaced video (field count > 1)? media_header mediaHeader; - status_t ret = fVideoTrack->ReadFrames(buffer, &frameCount, &mediaHeader); + ret = fVideoTrack->ReadFrames(buffer, &frameCount, &mediaHeader); if (ret < B_OK) { - printf("MediaTrackVideoSupplier::ReadFrame() - " - "error while reading frame of track: %s\n", strerror(ret)); + if (ret != B_LAST_BUFFER_ERROR) { + fprintf(stderr, "MediaTrackVideoSupplier::ReadFrame() - " + "error while reading frame of track: %s\n", strerror(ret)); + } } else { fPerformanceTime = mediaHeader.start_time; } + fCurrentFrame = fVideoTrack->CurrentFrame(); if (performanceTime) *performanceTime = fPerformanceTime; @@ -186,10 +150,52 @@ MediaTrackVideoSupplier::SeekToTime(bigtime_t* performanceTime) return B_NO_INIT; bigtime_t _performanceTime = *performanceTime; - status_t ret = fVideoTrack->SeekToTime(performanceTime); + status_t ret = fVideoTrack->FindKeyFrameForTime(performanceTime, + B_MEDIA_SEEK_CLOSEST_BACKWARD); + if (ret < B_OK) + return ret; + + ret = fVideoTrack->SeekToTime(performanceTime); if (ret == B_OK) { -printf("seeked: %lld -> %lld\n", _performanceTime, *performanceTime); +if (_performanceTime != *performanceTime) +printf("seeked by time: %lld -> %lld\n", _performanceTime, *performanceTime); fPerformanceTime = *performanceTime; + fCurrentFrame = fVideoTrack->CurrentFrame(); + } + + return ret; +} + + +status_t +MediaTrackVideoSupplier::SeekToFrame(int64* frame) +{ + if (!fVideoTrack) + return B_NO_INIT; + + int64 wantFrame = *frame; + int64 currentFrame = fVideoTrack->CurrentFrame(); + + if (wantFrame == currentFrame) + return B_OK; + + status_t ret = fVideoTrack->FindKeyFrameForFrame(frame, + B_MEDIA_SEEK_CLOSEST_BACKWARD); + if (ret < B_OK) + return ret; + + if (*frame < currentFrame && wantFrame > currentFrame) { + *frame = currentFrame; + return B_OK; + } + +if (wantFrame != *frame) +printf("seeked by frame: %lld -> %lld\n", wantFrame, *frame); + + ret = fVideoTrack->SeekToFrame(frame); + if (ret == B_OK) { + fCurrentFrame = *frame; + fPerformanceTime = fVideoTrack->CurrentTime(); } return ret; @@ -297,3 +303,69 @@ string_for_color_space(color_space format) } return name; } + + +status_t +MediaTrackVideoSupplier::_SwitchFormat(color_space format, int32 bytesPerRow) +{ + // get the encoded format + memset(&fFormat, 0, sizeof(media_format)); + status_t ret = fVideoTrack->EncodedFormat(&fFormat); + if (ret < B_OK) { + printf("MediaTrackVideoSupplier::_SwitchFormat() - " + "fVideoTrack->EncodedFormat(): %s\n", strerror(ret)); + return ret; + } + + // get ouput video frame size + uint32 width = fFormat.u.encoded_video.output.display.line_width; + uint32 height = fFormat.u.encoded_video.output.display.line_count; + + // specifiy the decoded format. we derive this information from + // the encoded format (width & height). + memset(&fFormat, 0, sizeof(media_format)); +// fFormat.u.raw_video.last_active = height - 1; +// fFormat.u.raw_video.orientation = B_VIDEO_TOP_LEFT_RIGHT; +// fFormat.u.raw_video.pixel_width_aspect = 1; +// fFormat.u.raw_video.pixel_height_aspect = 1; + fFormat.u.raw_video.display.format = format; + fFormat.u.raw_video.display.line_width = width; + fFormat.u.raw_video.display.line_count = height; + int32 minBytesPerRow; + if (format == B_RGB32 || format == B_RGBA32) + minBytesPerRow = width * 4; + else if (format == B_YCbCr422) + minBytesPerRow = ((width * 2 + 3) / 4) * 4; + fFormat.u.raw_video.display.bytes_per_row = max_c(minBytesPerRow, + bytesPerRow); + + ret = fVideoTrack->DecodedFormat(&fFormat); + + if (ret < B_OK) { + printf("MediaTrackVideoSupplier::_SwitchFormat() - " + "fVideoTrack->DecodedFormat(): %s\n", strerror(ret)); + return ret; + } + + if (fFormat.u.raw_video.display.format != format) { + printf("MediaTrackVideoSupplier::_SwitchFormat() - " + " codec changed colorspace of decoded format (%s -> %s)!\n" + " this is bad for performance, since colorspace conversion\n" + " needs to happen during playback.\n", + string_for_color_space(format), + string_for_color_space(fFormat.u.raw_video.display.format)); + // check if the codec forgot to adjust bytes_per_row + uint32 minBPR; + format = fFormat.u.raw_video.display.format; + if (format == B_YCbCr422) + minBPR = ((width * 2 + 3) / 4) * 4; + else + minBPR = width * 4; + if (minBPR != fFormat.u.raw_video.display.bytes_per_row) { + printf(" -> stupid codec forgot to adjust bytes_per_row!\n"); + fFormat.u.raw_video.display.bytes_per_row = minBPR; + fVideoTrack->DecodedFormat(&fFormat); + } + } + return ret; +} diff --git a/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h index 92823ee5d3..adc97454be 100644 --- a/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h +++ b/src/apps/mediaplayer/supplier/MediaTrackVideoSupplier.h @@ -1,5 +1,5 @@ /* - * Copyright 2007, Haiku. All rights reserved. + * Copyright 2007-2008, Haiku. All rights reserved. * Distributed under the terms of the MIT License. * * Authors: @@ -8,40 +8,51 @@ #ifndef MEDIA_TRACK_VIDEO_SUPPLIER_H #define MEDIA_TRACK_VIDEO_SUPPLIER_H -#include "VideoSupplier.h" +#include "VideoTrackSupplier.h" + +#include class BMediaTrack; -class MediaTrackVideoSupplier : public VideoSupplier { +class MediaTrackVideoSupplier : public VideoTrackSupplier { public: MediaTrackVideoSupplier(BMediaTrack* track, color_space preferredFormat); virtual ~MediaTrackVideoSupplier(); - virtual media_format Format() const; + virtual const media_format& Format() const; virtual status_t GetEncodedFormat(media_format* format) const; virtual status_t GetCodecInfo(media_codec_info* info) const; virtual status_t ReadFrame(void* buffer, - bigtime_t* performanceTime); + bigtime_t* performanceTime, + const media_format* format, + bool& wasCached); virtual status_t SeekToTime(bigtime_t* performanceTime); + virtual status_t SeekToFrame(int64* frame); virtual bigtime_t Position() const { return fPerformanceTime; } virtual bigtime_t Duration() const { return fDuration; } + virtual int64 CurrentFrame() const + { return fCurrentFrame; } virtual BRect Bounds() const; virtual color_space ColorSpace() const; virtual uint32 BytesPerRow() const; private: + status_t _SwitchFormat(color_space format, + int32 bytesPerRow); + BMediaTrack* fVideoTrack; media_format fFormat; bigtime_t fPerformanceTime; bigtime_t fDuration; + int64 fCurrentFrame; }; #endif // MEDIA_TRACK_VIDEO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/ProxyAudioSupplier.cpp b/src/apps/mediaplayer/supplier/ProxyAudioSupplier.cpp new file mode 100644 index 0000000000..761551a053 --- /dev/null +++ b/src/apps/mediaplayer/supplier/ProxyAudioSupplier.cpp @@ -0,0 +1,291 @@ +/* + * Copyright © 2008 Stephan Aßmus + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include "ProxyAudioSupplier.h" + +#include +#include +#include +#include + +#include +#include + +#include "AudioAdapter.h" +#include "AudioTrackSupplier.h" +#include "PlaybackManager.h" + +using std::nothrow; + + +//#define TRACE_PROXY_AUDIO_SUPPLIER +#ifdef TRACE_PROXY_AUDIO_SUPPLIER +# define TRACE(x...) printf("ProxyAudioSupplier::"); printf(x) +#else +# define TRACE(x...) +#endif + + +struct PlayingInterval { + PlayingInterval(bigtime_t startTime, bigtime_t endTime) + : start_time(startTime) + , end_time(endTime) + { + } + + bigtime_t start_time; + bigtime_t end_time; + bigtime_t x_start_time; + bigtime_t x_end_time; + float speed; +}; + + +ProxyAudioSupplier::ProxyAudioSupplier(PlaybackManager* playbackManager) + : fPlaybackManager(playbackManager) + , fVideoFrameRate(25.0) + + , fSupplier(NULL) + , fAdapter(NULL) + , fAudioResampler() +{ + TRACE("ProxyAudioSupplier()\n"); +} + + +ProxyAudioSupplier::~ProxyAudioSupplier() +{ + TRACE("~ProxyAudioSupplier()\n"); + delete fAdapter; +} + + +status_t +ProxyAudioSupplier::GetFrames(void* buffer, int64 frameCount, + bigtime_t startTime, bigtime_t endTime) +{ + TRACE("GetFrames(%p, %lld, %lld, %lld)\n", buffer, frameCount, + startTime, endTime); + + // Create a list of playing intervals which compose the supplied + // performance time interval. + BList playingIntervals; + status_t error = fPlaybackManager->LockWithTimeout(10000); + if (error == B_OK) { + bigtime_t intervalStartTime = startTime; + while (intervalStartTime < endTime) { + PlayingInterval* interval + = new (nothrow) PlayingInterval(intervalStartTime, endTime); + if (!interval) { + error = B_NO_MEMORY; + break; + } + fPlaybackManager->GetPlaylistTimeInterval( + interval->start_time, interval->end_time, + interval->x_start_time, interval->x_end_time, + interval->speed); + if (!playingIntervals.AddItem(interval)) { + delete interval; + error = B_NO_MEMORY; + break; + } + intervalStartTime = interval->end_time; + } + fPlaybackManager->SetCurrentAudioTime(endTime); + fPlaybackManager->Unlock(); + } else if (error == B_TIMED_OUT) { + TRACE("GetFrames() - LOCKING THE PLAYBACK MANAGER TIMED OUT!!!\n"); + } + + // retrieve the audio data for each interval. + int64 framesRead = 0; + while (!playingIntervals.IsEmpty()) { + PlayingInterval* interval + = (PlayingInterval*)playingIntervals.RemoveItem(0L); + if (error != B_OK) { + delete interval; + continue; + } + + TRACE("GetFrames() - interval [%lld, %lld]: [%lld, %lld]\n", + interval->start_time, interval->end_time, + interval->x_start_time, interval->x_end_time); + + // get playing direction + int32 playingDirection = 0; + if (interval->speed > 0) + playingDirection = 1; + else if (interval->speed < 0) + playingDirection = -1; + float absSpeed = interval->speed * playingDirection; + int64 framesToRead = _AudioFrameForTime(interval->end_time) + - _AudioFrameForTime(interval->start_time); + // not playing + if (absSpeed == 0) + _ReadSilence(buffer, framesToRead); + // playing + else { + fAudioResampler.SetInOffset( + _AudioFrameForTime(interval->x_start_time)); + fAudioResampler.SetTimeScale(absSpeed); + error = fAudioResampler.Read(buffer, 0, framesToRead); + // backwards -> reverse frames + if (error == B_OK && interval->speed < 0) + _ReverseFrames(buffer, framesToRead); + } + // read silence on error + if (error != B_OK) { + _ReadSilence(buffer, framesToRead); + error = B_OK; + } + framesRead += framesToRead; + buffer = _SkipFrames(buffer, framesToRead); + delete interval; + } + // read silence on error + if (error != B_OK) { + _ReadSilence(buffer, frameCount); + error = B_OK; + } + + TRACE("GetFrames() done\n"); + + return error; +} + + +void +ProxyAudioSupplier::SetFormat(const media_format& format) +{ +//printf("ProxyAudioSupplier::SetFormat()\n"); + #ifdef TRACE_PROXY_AUDIO_SUPPLIER + char string[256]; + string_for_format(format, string, 256); + TRACE("SetFormat(%s)\n", string); + #endif + + fAudioResampler.SetFormat(format); + + // In case SetSupplier was called before, we need + // to adapt to the new format, or maybe the format + // was still invalid. + SetSupplier(fSupplier, fVideoFrameRate); +} + + +const media_format& +ProxyAudioSupplier::Format() const +{ + return fAudioResampler.Format(); +} + + +status_t +ProxyAudioSupplier::InitCheck() const +{ + status_t ret = AudioSupplier::InitCheck(); + if (ret < B_OK) + return ret; + return B_OK; +} + + +void +ProxyAudioSupplier::SetSupplier(AudioTrackSupplier* supplier, + float videoFrameRate) +{ +//printf("ProxyAudioSupplier::SetSupplier(%p, %.1f)\n", supplier, +//videoFrameRate); + TRACE("SetSupplier(%p, %.1f)\n", supplier, videoFrameRate); + + fSupplier = supplier; + fVideoFrameRate = videoFrameRate; + + delete fAdapter; + fAdapter = new AudioAdapter(fSupplier, Format()); + + fAudioResampler.SetSource(fAdapter); +} + + +// #pragma mark - audio/video/frame/time conversion + +int64 +ProxyAudioSupplier::_AudioFrameForVideoFrame(int64 frame) const +{ + if (!fSupplier) { + return (int64)((double)frame * Format().u.raw_audio.frame_rate + / fVideoFrameRate); + } + const media_format& format = fSupplier->Format(); + return (int64)((double)frame * format.u.raw_audio.frame_rate + / fVideoFrameRate); +} + + +int64 +ProxyAudioSupplier::_VideoFrameForAudioFrame(int64 frame) const +{ + if (!fSupplier) { + return (int64)((double)frame * fVideoFrameRate + / Format().u.raw_audio.frame_rate); + } + + const media_format& format = fSupplier->Format(); + return (int64)((double)frame * fVideoFrameRate + / format.u.raw_audio.frame_rate); +} + + +int64 +ProxyAudioSupplier::_AudioFrameForTime(bigtime_t time) const +{ + return (int64)((double)time * Format().u.raw_audio.frame_rate + / 1000000.0); +} + + +int64 +ProxyAudioSupplier::_VideoFrameForTime(bigtime_t time) const +{ + return (int64)((double)time * fVideoFrameRate / 1000000.0); +} + + +// #pragma mark - utility + + +void +ProxyAudioSupplier::_ReadSilence(void* buffer, int64 frames) const +{ + memset(buffer, 0, (char*)_SkipFrames(buffer, frames) - (char*)buffer); +} + + +void +ProxyAudioSupplier::_ReverseFrames(void* buffer, int64 frames) const +{ + int32 sampleSize = Format().u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 frameSize = sampleSize * Format().u.raw_audio.channel_count; + char* front = (char*)buffer; + char* back = (char*)buffer + (frames - 1) * frameSize; + while (front < back) { + for (int32 i = 0; i < frameSize; i++) + swap(front[i], back[i]); + front += frameSize; + back -= frameSize; + } +} + + +void* +ProxyAudioSupplier::_SkipFrames(void* buffer, int64 frames) const +{ + int32 sampleSize = Format().u.raw_audio.format + & media_raw_audio_format::B_AUDIO_SIZE_MASK; + int32 frameSize = sampleSize * Format().u.raw_audio.channel_count; + return (char*)buffer + frames * frameSize; +} + diff --git a/src/apps/mediaplayer/supplier/ProxyAudioSupplier.h b/src/apps/mediaplayer/supplier/ProxyAudioSupplier.h new file mode 100644 index 0000000000..e8194cb8d9 --- /dev/null +++ b/src/apps/mediaplayer/supplier/ProxyAudioSupplier.h @@ -0,0 +1,57 @@ +/* + * Copyright © 2008 Stephan Aßmus + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef PROXY_AUDIO_SUPPLIER_H +#define PROXY_AUDIO_SUPPLIER_H + + +#include "AudioResampler.h" +#include "AudioSupplier.h" + + +class AudioTrackSupplier; +class PlaybackManager; + + +class ProxyAudioSupplier : public AudioSupplier { +public: + ProxyAudioSupplier( + PlaybackManager* playbackManager); + virtual ~ProxyAudioSupplier(); + + // AudioSupplier interface + virtual status_t GetFrames(void* buffer, int64 frameCount, + bigtime_t startTime, bigtime_t endTime); + + virtual void SetFormat(const media_format& format); + virtual const media_format& Format() const; + + virtual status_t InitCheck() const; + + // ProxyAudioSupplier + void SetSupplier(AudioTrackSupplier* supplier, + float videoFrameRate); + +private: + int64 _AudioFrameForVideoFrame(int64 frame) const; + int64 _VideoFrameForAudioFrame(int64 frame) const; + int64 _AudioFrameForTime(bigtime_t time) const; + int64 _VideoFrameForTime(bigtime_t time) const; + + void _ReadSilence(void* buffer, int64 frames) const; + void _ReverseFrames(void* buffer, + int64 frames) const; + void* _SkipFrames(void* buffer, int64 frames) const; + +private: + PlaybackManager* fPlaybackManager; + float fVideoFrameRate; + + AudioTrackSupplier* fSupplier; + AudioReader* fAdapter; + AudioResampler fAudioResampler; +}; + + +#endif // PROXY_AUDIO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp b/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp new file mode 100644 index 0000000000..2fd0ce0e9d --- /dev/null +++ b/src/apps/mediaplayer/supplier/ProxyVideoSupplier.cpp @@ -0,0 +1,65 @@ +/* + * Copyright © 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#include "ProxyVideoSupplier.h" + +#include + +#include + +#include "VideoTrackSupplier.h" + + +ProxyVideoSupplier::ProxyVideoSupplier() + : fSupplier(NULL) +{ +} + + +ProxyVideoSupplier::~ProxyVideoSupplier() +{ +} + + +status_t +ProxyVideoSupplier::FillBuffer(int64 startFrame, void* buffer, + const media_format* format, bool& wasCached) +{ +//printf("ProxyVideoSupplier::FillBuffer(%lld)\n", startFrame); + if (fSupplier == NULL) + return B_NO_INIT; + + bigtime_t performanceTime = 0; + if (fSupplier->CurrentFrame() != startFrame) { + int64 frame = startFrame; + status_t ret = fSupplier->SeekToFrame(&frame); + if (ret != B_OK) + return ret; + while (frame < (startFrame - 1)) { + ret = fSupplier->ReadFrame(buffer, &performanceTime, format, + wasCached); + if (ret != B_OK) + return ret; + frame++; + } + } + + // TODO: cache into intermediate buffer! + + return fSupplier->ReadFrame(buffer, &performanceTime, format, wasCached); +} + + +void +ProxyVideoSupplier::DeleteCaches() +{ +} + + +void +ProxyVideoSupplier::SetSupplier(VideoTrackSupplier* supplier) +{ + fSupplier = supplier; +} + diff --git a/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h b/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h new file mode 100644 index 0000000000..505989c387 --- /dev/null +++ b/src/apps/mediaplayer/supplier/ProxyVideoSupplier.h @@ -0,0 +1,32 @@ +/* + * Copyright © 2008 Stephan Aßmus + * All rights reserved. Distributed under the terms of the MIT licensce. + */ +#ifndef PROXY_VIDEO_SUPPLIER_H +#define PROXY_VIDEO_SUPPLIER_H + + +#include "VideoSupplier.h" + + +class VideoTrackSupplier; + + +class ProxyVideoSupplier : public VideoSupplier { +public: + ProxyVideoSupplier(); + virtual ~ProxyVideoSupplier(); + + virtual status_t FillBuffer(int64 startFrame, void* buffer, + const media_format* format, + bool& wasCached); + + virtual void DeleteCaches(); + + void SetSupplier(VideoTrackSupplier* supplier); + +private: + VideoTrackSupplier* fSupplier; +}; + +#endif // PROXY_VIDEO_SUPPLIER_H diff --git a/src/apps/mediaplayer/supplier/VideoSupplier.cpp b/src/apps/mediaplayer/supplier/VideoTrackSupplier.cpp similarity index 60% rename from src/apps/mediaplayer/supplier/VideoSupplier.cpp rename to src/apps/mediaplayer/supplier/VideoTrackSupplier.cpp index b02f51c030..842b2f718e 100644 --- a/src/apps/mediaplayer/supplier/VideoSupplier.cpp +++ b/src/apps/mediaplayer/supplier/VideoTrackSupplier.cpp @@ -5,15 +5,15 @@ * Authors: * Stephan Aßmus */ -#include "VideoSupplier.h" +#include "VideoTrackSupplier.h" -VideoSupplier::VideoSupplier() +VideoTrackSupplier::VideoTrackSupplier() { } -VideoSupplier::~VideoSupplier() +VideoTrackSupplier::~VideoTrackSupplier() { } diff --git a/src/apps/mediaplayer/supplier/VideoSupplier.h b/src/apps/mediaplayer/supplier/VideoTrackSupplier.h similarity index 56% rename from src/apps/mediaplayer/supplier/VideoSupplier.h rename to src/apps/mediaplayer/supplier/VideoTrackSupplier.h index 405a1e5342..383ee4a3dc 100644 --- a/src/apps/mediaplayer/supplier/VideoSupplier.h +++ b/src/apps/mediaplayer/supplier/VideoTrackSupplier.h @@ -5,28 +5,32 @@ * Authors: * Stephan Aßmus */ -#ifndef VIDEO_SUPPLIER_H -#define VIDEO_SUPPLIER_H +#ifndef VIDEO_TRACK_SUPPLIER_H +#define VIDEO_TRACK_SUPPLIER_H #include #include -class VideoSupplier { +class VideoTrackSupplier { public: - VideoSupplier(); - virtual ~VideoSupplier(); + VideoTrackSupplier(); + virtual ~VideoTrackSupplier(); - virtual media_format Format() const = 0; + virtual const media_format& Format() const = 0; virtual status_t GetEncodedFormat(media_format* format) const = 0; virtual status_t GetCodecInfo(media_codec_info* info) const = 0; virtual status_t ReadFrame(void* buffer, - bigtime_t* performanceTime) = 0; + bigtime_t* performanceTime, + const media_format* format, + bool& wasCached) = 0; virtual status_t SeekToTime(bigtime_t* performanceTime) = 0; + virtual status_t SeekToFrame(int64* frame) = 0; virtual bigtime_t Position() const = 0; virtual bigtime_t Duration() const = 0; + virtual int64 CurrentFrame() const = 0; }; -#endif // VIDEO_SUPPLIER_H +#endif // VIDEO_TRACK_SUPPLIER_H diff --git a/src/apps/mediaplayer/support/AutoLocker.h b/src/apps/mediaplayer/support/AutoLocker.h deleted file mode 100644 index ea568ebc61..0000000000 --- a/src/apps/mediaplayer/support/AutoLocker.h +++ /dev/null @@ -1,151 +0,0 @@ -/* - * Copyright 2004-2006, Haiku. - * Distributed under the terms of the MIT License. - * - * Authors: - * IngoWeinhold - */ - -/** Scope-based automatic deletion of objects/arrays. - * ObjectDeleter - deletes an object - * ArrayDeleter - deletes an array - * MemoryDeleter - free()s malloc()ed memory - */ - -#ifndef AUTO_LOCKER_H -#define AUTO_LOCKER_H - -#include - -// locking - -// AutoLockerStandardLocking -template -class AutoLockerStandardLocking { -public: - inline bool Lock(Lockable *lockable) - { - return lockable->Lock(); - } - - inline void Unlock(Lockable *lockable) - { - lockable->Unlock(); - } -}; - -// AutoLockerReadLocking -template -class AutoLockerReadLocking { -public: - inline bool Lock(Lockable *lockable) - { - return lockable->ReadLock(); - } - - inline void Unlock(Lockable *lockable) - { - lockable->ReadUnlock(); - } -}; - -// AutoLockerWriteLocking -template -class AutoLockerWriteLocking { -public: - inline bool Lock(Lockable *lockable) - { - return lockable->WriteLock(); - } - - inline void Unlock(Lockable *lockable) - { - lockable->WriteUnlock(); - } -}; - -// AutoLocker -template > -class AutoLocker { -private: - typedef AutoLocker ThisClass; -public: - inline AutoLocker(Lockable *lockable, bool alreadyLocked = false) - : fLockable(lockable), - fLocked(fLockable && alreadyLocked) - { - if (!fLocked) - _Lock(); - } - - inline AutoLocker(Lockable &lockable, bool alreadyLocked = false) - : fLockable(&lockable), - fLocked(fLockable && alreadyLocked) - { - if (!fLocked) - _Lock(); - } - - inline ~AutoLocker() - { - Unlock(); - } - - inline void SetTo(Lockable *lockable, bool alreadyLocked) - { - Unlock(); - fLockable = lockable; - fLocked = alreadyLocked; - if (!fLocked) - _Lock(); - } - - inline void SetTo(Lockable &lockable, bool alreadyLocked) - { - SetTo(&lockable, alreadyLocked); - } - - inline void Unset() - { - Unlock(); - } - - inline AutoLocker &operator=(Lockable *lockable) - { - SetTo(lockable); - return *this; - } - - inline AutoLocker &operator=(Lockable &lockable) - { - SetTo(&lockable); - return *this; - } - - inline bool IsLocked() const { return fLocked; } - - inline void Unlock() - { - if (fLockable && fLocked) { - fLocking.Unlock(fLockable); - fLocked = false; - } - } - - inline operator bool() const { return fLocked; } - -private: - inline void _Lock() - { - if (fLockable) - fLocked = fLocking.Lock(fLockable); - } - -private: - Lockable *fLockable; - bool fLocked; - Locking fLocking; -}; - -#endif // AUTO_LOCKER_H diff --git a/src/apps/mediaplayer/support/Event.cpp b/src/apps/mediaplayer/support/Event.cpp new file mode 100644 index 0000000000..91d887b8c0 --- /dev/null +++ b/src/apps/mediaplayer/support/Event.cpp @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include + +#include "Event.h" + + +Event::Event(bool autoDelete) + : fTime(0), + fAutoDelete(autoDelete) +{ +} + + +Event::Event(bigtime_t time, bool autoDelete) + : fTime(time), + fAutoDelete(autoDelete) +{ +} + + +Event::~Event() +{ +} + + +void +Event::SetTime(bigtime_t time) +{ + fTime = time; +} + + +bigtime_t +Event::Time() const +{ + return fTime; +} + + +void +Event::SetAutoDelete(bool autoDelete) +{ + fAutoDelete = autoDelete; +} + + +void +Event::Execute() +{ + printf("Event::Execute() - %Ld\n", fTime); +} + diff --git a/src/apps/mediaplayer/support/Event.h b/src/apps/mediaplayer/support/Event.h new file mode 100644 index 0000000000..c94b5a2097 --- /dev/null +++ b/src/apps/mediaplayer/support/Event.h @@ -0,0 +1,33 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef EVENT_H +#define EVENT_H + + +#include + + +class Event { + public: + Event(bool autoDelete = true); + Event(bigtime_t time, bool autoDelete = true); + virtual ~Event(); + + void SetTime(bigtime_t time); + bigtime_t Time() const; + + void SetAutoDelete(bool autoDelete); + bool AutoDelete() const + { return fAutoDelete; } + + virtual void Execute(); + + private: + bigtime_t fTime; + bool fAutoDelete; +}; + +#endif // EVENT_H diff --git a/src/apps/mediaplayer/support/EventQueue.cpp b/src/apps/mediaplayer/support/EventQueue.cpp new file mode 100644 index 0000000000..8c878e2542 --- /dev/null +++ b/src/apps/mediaplayer/support/EventQueue.cpp @@ -0,0 +1,210 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include +#include + +#include "Event.h" + +#include "EventQueue.h" + + +EventQueue::EventQueue() + : fEvents(100), + fEventExecutor(-1), + fThreadControl(-1), + fNextEventTime(0), + fStatus(B_ERROR) + +{ + fThreadControl = create_sem(0, "event queue control"); + if (fThreadControl >= B_OK) + fStatus = B_OK; + else + fStatus = fThreadControl; + if (fStatus == B_OK) { + fEventExecutor = spawn_thread(_execute_events_, "event queue runner", + B_NORMAL_PRIORITY, this); + if (fEventExecutor >= B_OK) { + fStatus = B_OK; + resume_thread(fEventExecutor); + } else + fStatus = fEventExecutor; + } +} + + +EventQueue::~EventQueue() +{ + if (delete_sem(fThreadControl) == B_OK) + wait_for_thread(fEventExecutor, &fEventExecutor); + while (Event *event = (Event*)fEvents.RemoveItem(0L)) { + if (event->AutoDelete()) + delete event; + } +} + + +status_t +EventQueue::InitCheck() +{ + return fStatus; +} + + +EventQueue* +EventQueue::CreateDefault() +{ + if (!fDefaultQueue) { + fDefaultQueue = new(nothrow) EventQueue; + if (fDefaultQueue && fDefaultQueue->InitCheck() != B_OK) + DeleteDefault(); + } + return fDefaultQueue; +} + + +void +EventQueue::DeleteDefault() +{ + if (fDefaultQueue) { + delete fDefaultQueue; + fDefaultQueue = NULL; + } +} + + +EventQueue& +EventQueue::Default() +{ + return *fDefaultQueue; +} + + +void +EventQueue::AddEvent(Event* event) +{ + Lock(); + _AddEvent(event); + _Reschedule(); + Unlock(); +} + + +bool +EventQueue::RemoveEvent(Event* event) +{ + bool result = false; + Lock(); + if ((result = fEvents.RemoveItem(event))) + _Reschedule(); + Unlock(); + return result; +} + + +void +EventQueue::ChangeEvent(Event* event, bigtime_t newTime) +{ + Lock(); + if (fEvents.RemoveItem(event)) { + event->SetTime(newTime); + _AddEvent(event); + _Reschedule(); + } + Unlock(); +} + + +// PRE: The object must be locked. +void +EventQueue::_AddEvent(Event* event) +{ + // find the insertion index + int32 lower = 0; + int32 upper = fEvents.CountItems(); + while (lower < upper) { + int32 mid = (lower + upper) / 2; + Event* midEvent = _EventAt(mid); + if (event->Time() < midEvent->Time()) + upper = mid; + else + lower = mid + 1; + } + fEvents.AddItem(event, lower); +} + + +Event* +EventQueue::_EventAt(int32 index) const +{ + return (Event*)fEvents.ItemAtFast(index); +} + + +int32 +EventQueue::_execute_events_(void* cookie) +{ + EventQueue *gc = (EventQueue*)cookie; + return gc->_ExecuteEvents(); +} + + +int32 +EventQueue::_ExecuteEvents() +{ + bool running = true; + while (running) { + bigtime_t waitUntil = B_INFINITE_TIMEOUT; + if (Lock()) { + if (!fEvents.IsEmpty()) + waitUntil = _EventAt(0)->Time(); + fNextEventTime = waitUntil; + Unlock(); + } + status_t err = acquire_sem_etc(fThreadControl, 1, B_ABSOLUTE_TIMEOUT, + waitUntil); + switch (err) { + case B_TIMED_OUT: + // execute events, that are supposed to go off + if (Lock()) { + while (!fEvents.IsEmpty() + && system_time() >= _EventAt(0)->Time()) { + Event* event = (Event*)fEvents.RemoveItem(0L); + bool deleteEvent = event->AutoDelete(); + event->Execute(); + if (deleteEvent) + delete event; + } + Unlock(); + } + break; + case B_BAD_SEM_ID: + running = false; + break; + case B_OK: + default: + break; + } + } + return 0; +} + + +// PRE: The object must be locked. +void +EventQueue::_Reschedule() +{ + if (fStatus == B_OK) { + if (!fEvents.IsEmpty() && _EventAt(0)->Time() < fNextEventTime) + release_sem(fThreadControl); + } +} + + +// static variables + +EventQueue* EventQueue::fDefaultQueue = NULL; + diff --git a/src/apps/mediaplayer/support/EventQueue.h b/src/apps/mediaplayer/support/EventQueue.h new file mode 100644 index 0000000000..24b0ce082e --- /dev/null +++ b/src/apps/mediaplayer/support/EventQueue.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef EVENT_QUEUE_H +#define EVENT_QUEUE_H + + +#include +#include +#include + + +class Event; + + +class EventQueue : public BLocker { + public: + EventQueue(); + virtual ~EventQueue(); + + status_t InitCheck(); + + static EventQueue* CreateDefault(); + static void DeleteDefault(); + static EventQueue& Default(); + + void AddEvent(Event* event); + bool RemoveEvent(Event* event); + void ChangeEvent(Event* event, + bigtime_t newTime); + + private: + void _AddEvent(Event* event); + Event* _EventAt(int32 index) const; + + static int32 _execute_events_(void *cookie); + int32 _ExecuteEvents(); + void _Reschedule(); + + BList fEvents; + thread_id fEventExecutor; + sem_id fThreadControl; + volatile bigtime_t fNextEventTime; + status_t fStatus; + static EventQueue* fDefaultQueue; +}; + +#endif // EVENT_QUEUE_H diff --git a/src/apps/mediaplayer/support/MessageEvent.cpp b/src/apps/mediaplayer/support/MessageEvent.cpp new file mode 100644 index 0000000000..bf17ec8e9c --- /dev/null +++ b/src/apps/mediaplayer/support/MessageEvent.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#include + +#include "MessageEvent.h" + + +MessageEvent::MessageEvent(bigtime_t time, BHandler* handler, uint32 command) + : Event(time), + AbstractLOAdapter(handler), + fCommand(command) +{ +} + + +MessageEvent::MessageEvent(bigtime_t time, const BMessenger& messenger) + : Event(time), + AbstractLOAdapter(messenger) +{ +} + + +MessageEvent::~MessageEvent() +{ +} + + +void +MessageEvent::Execute() +{ + BMessage msg(fCommand); + msg.AddInt64("time", Time()); + DeliverMessage(msg); +} + diff --git a/src/apps/mediaplayer/support/MessageEvent.h b/src/apps/mediaplayer/support/MessageEvent.h new file mode 100644 index 0000000000..96c5ada696 --- /dev/null +++ b/src/apps/mediaplayer/support/MessageEvent.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2000-2008, Ingo Weinhold , + * Copyright (c) 2000-2008, Stephan Aßmus , + * All Rights Reserved. Distributed under the terms of the MIT license. + */ +#ifndef MESSAGE_EVENT_H +#define MESSAGE_EVENT_H + + +#include "AbstractLOAdapter.h" +#include "Event.h" + + +enum { + MSG_EVENT = 'evnt', +}; + + +class MessageEvent : public Event, public AbstractLOAdapter { + public: + MessageEvent(bigtime_t time, + BHandler* handler, + uint32 command = MSG_EVENT); + MessageEvent(bigtime_t time, + const BMessenger& messenger); + virtual ~MessageEvent(); + + virtual void Execute(); + + private: + uint32 fCommand; +}; + +#endif // MESSAGE_EVENT_H