with an unsupported sound card or the like, the fSoundOutput initialization is checked in more places. git-svn-id: file:///srv/svn/repos/haiku/haiku/trunk@23764 a95241bf-73f2-0310-859d-f6bbb57e9c96
1395 lines
28 KiB
C++
1395 lines
28 KiB
C++
/*
|
|
* Controller.cpp - Media Player for the Haiku Operating System
|
|
*
|
|
* Copyright (C) 2006 Marcus Overhagen <[email protected]>
|
|
* Copyright (C) 2007 Stephan Aßmus <[email protected]>
|
|
* Copyright (C) 2007 Fredrik Modéen <[email protected]>
|
|
*
|
|
* 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 "Controller.h"
|
|
|
|
#include <new>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <Autolock.h>
|
|
#include <Bitmap.h>
|
|
#include <Debug.h>
|
|
#include <MediaFile.h>
|
|
#include <MediaTrack.h>
|
|
#include <Path.h>
|
|
#include <Window.h> // for debugging only
|
|
|
|
#include "ControllerView.h"
|
|
#include "PlaybackState.h"
|
|
#include "SoundOutput.h"
|
|
#include "VideoView.h"
|
|
|
|
// suppliers
|
|
#include "AudioSupplier.h"
|
|
#include "MediaTrackAudioSupplier.h"
|
|
#include "MediaTrackVideoSupplier.h"
|
|
#include "VideoSupplier.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)
|
|
{
|
|
if (err != B_OK) {
|
|
printf("%s. error 0x%08x (%s)\n",text, (int)err, strerror(err));
|
|
fflush(NULL);
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
Controller::Listener::Listener() {}
|
|
Controller::Listener::~Listener() {}
|
|
void Controller::Listener::FileFinished() {}
|
|
void Controller::Listener::FileChanged() {}
|
|
void Controller::Listener::VideoTrackChanged(int32) {}
|
|
void Controller::Listener::AudioTrackChanged(int32) {}
|
|
void Controller::Listener::VideoStatsChanged() {}
|
|
void Controller::Listener::AudioStatsChanged() {}
|
|
void Controller::Listener::PlaybackStateChanged(uint32) {}
|
|
void Controller::Listener::PositionChanged(float) {}
|
|
void Controller::Listener::VolumeChanged(float) {}
|
|
void Controller::Listener::MutedChanged(bool) {}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
Controller::Controller()
|
|
: fVideoView(NULL)
|
|
, fPaused(false)
|
|
, fStopped(true)
|
|
, fVolume(1.0)
|
|
, fMuted(false)
|
|
|
|
, fRef()
|
|
, fMediaFile(0)
|
|
, fDataLock("controller data lock")
|
|
|
|
, fVideoSupplier(NULL)
|
|
, fAudioSupplier(NULL)
|
|
|
|
, fVideoSupplierLock("video supplier lock")
|
|
, fAudioSupplierLock("audio supplier lock")
|
|
|
|
, 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)
|
|
, 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 -
|
|
|
|
|
|
bool
|
|
Controller::Lock()
|
|
{
|
|
return fDataLock.Lock();
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::LockWithTimeout(bigtime_t timeout)
|
|
{
|
|
return fDataLock.LockWithTimeout(timeout);
|
|
}
|
|
|
|
|
|
void
|
|
Controller::Unlock()
|
|
{
|
|
return fDataLock.Unlock();
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
status_t
|
|
Controller::SetTo(const entry_ref &ref)
|
|
{
|
|
BAutolock dl(fDataLock);
|
|
BAutolock al(fVideoSupplierLock);
|
|
BAutolock vl(fAudioSupplierLock);
|
|
|
|
if (fRef == ref)
|
|
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;
|
|
|
|
fSeekAudio = true;
|
|
fSeekVideo = true;
|
|
fSeekPosition = 0;
|
|
fPaused = false;
|
|
fStopped = fAutoplay ? false : true;
|
|
fPauseAtEndOfStream = false;
|
|
fSeekToStartAfterPause = false;
|
|
fTimeSourceSysTime = system_time();
|
|
fTimeSourcePerfTime = 0;
|
|
fDuration = 0;
|
|
|
|
_UpdatePosition(0);
|
|
|
|
status_t err;
|
|
|
|
BMediaFile *mf = new BMediaFile(&ref);
|
|
err = mf->InitCheck();
|
|
if (err != B_OK) {
|
|
printf("Controller::SetTo: initcheck failed\n");
|
|
delete mf;
|
|
_NotifyFileChanged();
|
|
return err;
|
|
}
|
|
|
|
int trackcount = mf->CountTracks();
|
|
if (trackcount <= 0) {
|
|
printf("Controller::SetTo: trackcount %d\n", trackcount);
|
|
delete mf;
|
|
_NotifyFileChanged();
|
|
return B_MEDIA_NO_HANDLER;
|
|
}
|
|
|
|
for (int i = 0; i < trackcount; i++) {
|
|
BMediaTrack *t = mf->TrackAt(i);
|
|
media_format f;
|
|
err = t->EncodedFormat(&f);
|
|
if (err != B_OK) {
|
|
printf("Controller::SetTo: EncodedFormat failed for track index %d, error 0x%08lx (%s)\n",
|
|
i, err, strerror(err));
|
|
mf->ReleaseTrack(t);
|
|
continue;
|
|
}
|
|
if (f.IsAudio()) {
|
|
fAudioTrackList->AddItem(t);
|
|
} else if (f.IsVideo()) {
|
|
fVideoTrackList->AddItem(t);
|
|
} else {
|
|
printf("Controller::SetTo: track index %d has unknown type\n", i);
|
|
mf->ReleaseTrack(t);
|
|
}
|
|
}
|
|
|
|
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());
|
|
|
|
fMediaFile = mf;
|
|
|
|
SelectAudioTrack(0);
|
|
SelectVideoTrack(0);
|
|
|
|
_NotifyFileChanged();
|
|
_NotifyPlaybackStateChanged();
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
Controller::GetSize(int *width, int *height)
|
|
{
|
|
BAutolock _(fVideoSupplierLock);
|
|
|
|
if (fVideoSupplier) {
|
|
media_format format = fVideoSupplier->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;
|
|
}
|
|
}
|
|
|
|
|
|
int
|
|
Controller::AudioTrackCount()
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fAudioTrackList->CountItems();
|
|
}
|
|
|
|
|
|
int
|
|
Controller::VideoTrackCount()
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fVideoTrackList->CountItems();
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::SelectAudioTrack(int n)
|
|
{
|
|
BAutolock _(fAudioSupplierLock);
|
|
|
|
BMediaTrack* track = (BMediaTrack *)fAudioTrackList->ItemAt(n);
|
|
if (!track)
|
|
return B_ERROR;
|
|
|
|
delete fAudioSupplier;
|
|
fAudioSupplier = new MediaTrackAudioSupplier(track);
|
|
|
|
bigtime_t a = fAudioSupplier->Duration();
|
|
bigtime_t v = fVideoSupplier ? fVideoSupplier->Duration() : 0;;
|
|
fDuration = max_c(a, v);
|
|
|
|
_NotifyAudioTrackChanged(n);
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::SelectVideoTrack(int n)
|
|
{
|
|
BAutolock _(fVideoSupplierLock);
|
|
|
|
BMediaTrack* track = (BMediaTrack *)fVideoTrackList->ItemAt(n);
|
|
if (!track)
|
|
return B_ERROR;
|
|
|
|
delete fVideoSupplier;
|
|
fVideoSupplier = new MediaTrackVideoSupplier(track,
|
|
IsOverlayActive() ? B_YCbCr422 : B_RGB32);
|
|
|
|
bigtime_t a = fAudioSupplier ? fAudioSupplier->Duration() : 0;
|
|
bigtime_t v = fVideoSupplier->Duration();
|
|
fDuration = max_c(a, v);
|
|
|
|
_NotifyVideoTrackChanged(n);
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
void
|
|
Controller::Stop()
|
|
{
|
|
printf("Controller::Stop\n");
|
|
|
|
BAutolock _(fDataLock);
|
|
|
|
if (fStopped)
|
|
return;
|
|
|
|
fPaused = false;
|
|
fStopped = true;
|
|
|
|
fSeekAudio = true;
|
|
fSeekVideo = true;
|
|
fSeekPosition = 0;
|
|
|
|
_NotifyPlaybackStateChanged();
|
|
_UpdatePosition(0, false, true);
|
|
}
|
|
|
|
|
|
void
|
|
Controller::Play()
|
|
{
|
|
printf("Controller::Play\n");
|
|
|
|
BAutolock _(fDataLock);
|
|
|
|
if (!fPaused && !fStopped)
|
|
return;
|
|
|
|
fStopped = false;
|
|
fPaused = false;
|
|
|
|
if (fSeekToStartAfterPause) {
|
|
printf("seeking to start after pause\n");
|
|
SetPosition(0);
|
|
fSeekToStartAfterPause = false;
|
|
}
|
|
|
|
_NotifyPlaybackStateChanged();
|
|
}
|
|
|
|
|
|
void
|
|
Controller::Pause()
|
|
{
|
|
printf("Controller::Pause\n");
|
|
|
|
BAutolock _(fDataLock);
|
|
|
|
if (fStopped || fPaused)
|
|
return;
|
|
|
|
fPaused = true;
|
|
|
|
_NotifyPlaybackStateChanged();
|
|
}
|
|
|
|
|
|
void
|
|
Controller::TogglePlaying()
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
if (IsPaused() || IsStopped())
|
|
Play();
|
|
else
|
|
Pause();
|
|
}
|
|
|
|
|
|
bool
|
|
Controller::IsPaused() const
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fPaused;
|
|
}
|
|
|
|
|
|
bool
|
|
Controller::IsStopped() const
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fStopped;
|
|
}
|
|
|
|
|
|
uint32
|
|
Controller::PlaybackState() const
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
if (fStopped)
|
|
return PLAYBACK_STATE_STOPPED;
|
|
if (fPaused)
|
|
return PLAYBACK_STATE_PAUSED;
|
|
return PLAYBACK_STATE_PLAYING;
|
|
}
|
|
|
|
|
|
bigtime_t
|
|
Controller::Duration()
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fDuration;
|
|
}
|
|
|
|
|
|
bigtime_t
|
|
Controller::Position()
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fPosition;
|
|
}
|
|
|
|
|
|
void
|
|
Controller::SetVolume(float value)
|
|
{
|
|
printf("Controller::SetVolume %.4f\n", value);
|
|
if (!Lock())
|
|
return;
|
|
|
|
value = max_c(0.0, min_c(2.0, value));
|
|
|
|
if (fVolume != value) {
|
|
if (fMuted)
|
|
ToggleMute();
|
|
|
|
fVolume = value;
|
|
if (fSoundOutput)
|
|
fSoundOutput->SetVolume(fVolume);
|
|
|
|
_NotifyVolumeChanged(fVolume);
|
|
}
|
|
|
|
Unlock();
|
|
}
|
|
|
|
void
|
|
Controller::VolumeUp()
|
|
{
|
|
// TODO: linear <-> exponential
|
|
SetVolume(Volume() + 0.05);
|
|
}
|
|
|
|
void
|
|
Controller::VolumeDown()
|
|
{
|
|
// TODO: linear <-> exponential
|
|
SetVolume(Volume() - 0.05);
|
|
}
|
|
|
|
void
|
|
Controller::ToggleMute()
|
|
{
|
|
if (!Lock())
|
|
return;
|
|
|
|
fMuted = !fMuted;
|
|
|
|
if (fSoundOutput) {
|
|
if (fMuted)
|
|
fSoundOutput->SetVolume(0.0);
|
|
else
|
|
fSoundOutput->SetVolume(fVolume);
|
|
}
|
|
|
|
_NotifyMutedChanged(fMuted);
|
|
|
|
Unlock();
|
|
}
|
|
|
|
|
|
float
|
|
Controller::Volume() const
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
return fVolume;
|
|
}
|
|
|
|
|
|
void
|
|
Controller::SetPosition(float value)
|
|
{
|
|
printf("Controller::SetPosition %.4f\n", value);
|
|
|
|
BAutolock _(fDataLock);
|
|
|
|
fSeekPosition = bigtime_t(value * Duration());
|
|
fSeekAudio = true;
|
|
fSeekVideo = true;
|
|
|
|
fSeekToStartAfterPause = false;
|
|
|
|
// release_sem(fAudioWaitSem);
|
|
// release_sem(fVideoWaitSem);
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
bool
|
|
Controller::HasFile()
|
|
{
|
|
// you need to hold the data lock
|
|
return fMediaFile != NULL;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetFileFormatInfo(media_file_format* fileFormat)
|
|
{
|
|
// you need to hold the data lock
|
|
if (!fMediaFile)
|
|
return B_NO_INIT;
|
|
return fMediaFile->GetFileFormatInfo(fileFormat);
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetCopyright(BString* copyright)
|
|
{
|
|
// you need to hold the data lock
|
|
if (!fMediaFile)
|
|
return B_NO_INIT;
|
|
*copyright = fMediaFile->Copyright();
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetLocation(BString* location)
|
|
{
|
|
// you need to hold the data lock
|
|
if (!fMediaFile)
|
|
return B_NO_INIT;
|
|
BPath path(&fRef);
|
|
status_t ret = path.InitCheck();
|
|
if (ret < B_OK)
|
|
return ret;
|
|
*location = "";
|
|
*location << "file://" << path.Path();
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetName(BString* name)
|
|
{
|
|
// you need to hold the data lock
|
|
if (!fMediaFile)
|
|
return B_NO_INIT;
|
|
*name = fRef.name;
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetEncodedVideoFormat(media_format* format)
|
|
{
|
|
// you need to hold the data lock
|
|
if (fVideoSupplier)
|
|
return fVideoSupplier->GetEncodedFormat(format);
|
|
return B_NO_INIT;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetVideoCodecInfo(media_codec_info* info)
|
|
{
|
|
// you need to hold the data lock
|
|
if (fVideoSupplier)
|
|
return fVideoSupplier->GetCodecInfo(info);
|
|
return B_NO_INIT;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetEncodedAudioFormat(media_format* format)
|
|
{
|
|
// you need to hold the data lock
|
|
if (fAudioSupplier)
|
|
return fAudioSupplier->GetEncodedFormat(format);
|
|
return B_NO_INIT;
|
|
}
|
|
|
|
|
|
status_t
|
|
Controller::GetAudioCodecInfo(media_codec_info* info)
|
|
{
|
|
// you need to hold the data lock
|
|
if (fAudioSupplier)
|
|
return fAudioSupplier->GetCodecInfo(info);
|
|
return B_NO_INIT;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
void
|
|
Controller::SetVideoView(VideoView *view)
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
fVideoView = view;
|
|
fVideoView->SetController(this);
|
|
}
|
|
|
|
|
|
bool
|
|
Controller::IsOverlayActive()
|
|
{
|
|
// return true;
|
|
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);
|
|
|
|
if (listener && !fListeners.HasItem(listener))
|
|
return fListeners.AddItem(listener);
|
|
return false;
|
|
}
|
|
|
|
|
|
void
|
|
Controller::RemoveListener(Listener* listener)
|
|
{
|
|
BAutolock _(fDataLock);
|
|
|
|
fListeners.RemoveItem(listener);
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
void
|
|
Controller::_AudioDecodeThread()
|
|
{
|
|
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)
|
|
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);
|
|
}
|
|
|
|
// status_t status = acquire_sem_etc(fVideoWaitSem, 1, B_ABSOLUTE_TIMEOUT, buffer->startTime);
|
|
// if (status != B_TIMED_OUT)
|
|
// return;
|
|
}
|
|
|
|
|
|
// #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)
|
|
return;
|
|
|
|
// NOTE: executed in audio/video decode thread
|
|
if (!fStopped) {
|
|
fPauseAtEndOfStream = true;
|
|
_NotifyFileFinished();
|
|
}
|
|
}
|
|
|
|
|
|
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<Controller *>(self)->_AudioDecodeThread();
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32
|
|
Controller::_VideoDecodeThreadEntry(void *self)
|
|
{
|
|
static_cast<Controller *>(self)->_VideoDecodeThread();
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32
|
|
Controller::_AudioPlayThreadEntry(void *self)
|
|
{
|
|
static_cast<Controller *>(self)->_AudioPlayThread();
|
|
return 0;
|
|
}
|
|
|
|
|
|
int32
|
|
Controller::_VideoPlayThreadEntry(void *self)
|
|
{
|
|
static_cast<Controller *>(self)->_VideoPlayThread();
|
|
return 0;
|
|
}
|
|
|
|
|
|
// #pragma mark -
|
|
|
|
|
|
void
|
|
Controller::_NotifyFileChanged()
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->FileChanged();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyFileFinished()
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->FileFinished();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyVideoTrackChanged(int32 index)
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->VideoTrackChanged(index);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyAudioTrackChanged(int32 index)
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->AudioTrackChanged(index);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyVideoStatsChanged()
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->VideoStatsChanged();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyAudioStatsChanged()
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->AudioStatsChanged();
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyPlaybackStateChanged()
|
|
{
|
|
uint32 state = PlaybackState();
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->PlaybackStateChanged(state);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyPositionChanged(float position)
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->PositionChanged(position);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyVolumeChanged(float volume)
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->VolumeChanged(volume);
|
|
}
|
|
}
|
|
|
|
|
|
void
|
|
Controller::_NotifyMutedChanged(bool muted)
|
|
{
|
|
BList listeners(fListeners);
|
|
int32 count = listeners.CountItems();
|
|
for (int32 i = 0; i < count; i++) {
|
|
Listener* listener = (Listener*)listeners.ItemAtFast(i);
|
|
listener->MutedChanged(muted);
|
|
}
|
|
}
|