From 13486faa75282a60222553ace90e3fd3f91fea48 Mon Sep 17 00:00:00 2001 From: havlenapetr Date: Tue, 20 Jul 2010 07:32:06 +0200 Subject: [PATCH 1/5] add better driver mapping methods Conflicts: jni/include/drivers_map.h --- jni/include/drivers_map.h | 52 +++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 13 deletions(-) diff --git a/jni/include/drivers_map.h b/jni/include/drivers_map.h index 6faaee0..7a6a050 100644 --- a/jni/include/drivers_map.h +++ b/jni/include/drivers_map.h @@ -1,25 +1,51 @@ #ifndef _DRIVERS_MAP_H_ #define _DRIVERS_MAP_H_ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// methods for access to audio on OS +typedef int (*audioDriver_register_t) (JNIEnv*, jobject); +typedef int (*audioDriver_set_t) (int,uint32_t,int,int,int); +typedef int (*audioDriver_start_t) (void); +typedef int (*audioDriver_flush_t) (void); +typedef int (*audioDriver_stop_t) (void); +typedef int (*audioDriver_reload_t) (void); +typedef int (*audioDriver_unregister_t) (void); +typedef int (*audioDriver_write_t) (void *, int); + +// methods for access to surface on OS +typedef int (*videoDriver_register_t) (JNIEnv*, jobject); +typedef int (*videoDriver_getPixels_t) (int width, int height, void** pixels); +typedef int (*videoDriver_updateSurface_t) (void); +typedef int (*videoDriver_unregister_t) (void); + // create pointer on system methods for accessing audio and video system #ifdef ANDROID #include #include -int (*AudioDriver_register) (void) = AndroidAudioTrack_register; -int (*AudioDriver_set) (int,uint32_t,int,int,int) = AndroidAudioTrack_set; -int (*AudioDriver_start) (void) = AndroidAudioTrack_start; -int (*AudioDriver_flush) (void) = AndroidAudioTrack_flush; -int (*AudioDriver_stop) (void) = AndroidAudioTrack_stop; -int (*AudioDriver_reload) (void) = AndroidAudioTrack_reload; -int (*AudioDriver_unregister) (void) = AndroidAudioTrack_unregister; -int (*AudioDriver_write) (void *, int) = AndroidAudioTrack_write; - -int (*VideoDriver_register) (JNIEnv*, jobject) = AndroidSurface_register; -int (*VideoDriver_getPixels) (int width, int height, void** pixels) = AndroidSurface_getPixels; -int (*VideoDriver_updateSurface) (void) = AndroidSurface_updateSurface; -int (*VideoDriver_unregister) (void) = AndroidSurface_unregister; +audioDriver_register_t AudioDriver_register = AndroidAudioTrack_register; +audioDriver_set_t AudioDriver_set = AndroidAudioTrack_set; +audioDriver_start_t AudioDriver_start = AndroidAudioTrack_start; +audioDriver_flush_t AudioDriver_flush = AndroidAudioTrack_flush; +audioDriver_stop_t AudioDriver_stop = AndroidAudioTrack_stop; +audioDriver_reload_t AudioDriver_reload = AndroidAudioTrack_reload; +audioDriver_write_t AudioDriver_write = AndroidAudioTrack_write; +audioDriver_unregister_t AudioDriver_unregister = AndroidAudioTrack_unregister; + +videoDriver_register_t VideoDriver_register = AndroidSurface_register; +videoDriver_getPixels_t VideoDriver_getPixels = AndroidSurface_getPixels; +videoDriver_updateSurface_t VideoDriver_updateSurface = AndroidSurface_updateSurface; +videoDriver_unregister_t VideoDriver_unregister = AndroidSurface_unregister; + +#ifdef __cplusplus +} // end of cpluplus +#endif #endif From 7ef242c60df471319c206ede27f42d7b1305c7e5 Mon Sep 17 00:00:00 2001 From: havlenapetr Date: Wed, 25 May 2011 13:29:39 +0200 Subject: [PATCH 2/5] implement new thread class --- jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp | 14 +- jni/libmediaplayer/decoder.cpp | 28 ++-- jni/libmediaplayer/decoder.h | 23 ++-- jni/libmediaplayer/decoder_audio.cpp | 2 +- jni/libmediaplayer/decoder_audio.h | 12 +- jni/libmediaplayer/decoder_video.cpp | 20 +-- jni/libmediaplayer/decoder_video.h | 21 +-- jni/libmediaplayer/mediaplayer.cpp | 14 +- jni/libmediaplayer/mediaplayer.h | 110 ++++++++-------- jni/libmediaplayer/thread.cpp | 90 +++++++------ jni/libmediaplayer/thread.h | 148 +++++++++++++++++++--- 11 files changed, 306 insertions(+), 176 deletions(-) diff --git a/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp b/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp index 2210a0b..7963b2a 100644 --- a/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp +++ b/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp @@ -86,9 +86,9 @@ static MediaPlayer* setMediaPlayer(JNIEnv* env, jobject thiz, MediaPlayer* playe { MediaPlayer* old = (MediaPlayer*)env->GetIntField(thiz, fields.context); if (old != NULL) { - __android_log_print(ANDROID_LOG_INFO, TAG, "freeing old mediaplayer object"); - free(old); - } + __android_log_print(ANDROID_LOG_INFO, TAG, "freeing old mediaplayer object"); + free(old); + } env->SetIntField(thiz, fields.context, (int)player); return old; } @@ -100,12 +100,12 @@ static MediaPlayer* setMediaPlayer(JNIEnv* env, jobject thiz, MediaPlayer* playe static void process_media_player_call(JNIEnv *env, jobject thiz, status_t opStatus, const char* exception, const char *message) { if (exception == NULL) { // Don't throw exception. Instead, send an event. - /* + /* if (opStatus != (status_t) OK) { sp mp = getMediaPlayer(env, thiz); if (mp != 0) mp->notify(MEDIA_ERROR, opStatus, 0); } - */ + */ } else { // Throw exception! if ( opStatus == (status_t) INVALID_OPERATION ) { jniThrowException(env, "java/lang/IllegalStateException", NULL); @@ -172,8 +172,8 @@ com_media_ffmpeg_FFMpegPlayer_setVideoSurface(JNIEnv *env, jobject thiz, jobject jniThrowException(env, "java/lang/IllegalStateException", NULL); return; } - process_media_player_call( env, thiz, mp->setVideoSurface(env, jsurface), - "java/io/IOException", "Set video surface failed."); + process_media_player_call( env, thiz, mp->setVideoSurface(env, jsurface), + "java/io/IOException", "Set video surface failed."); } static void diff --git a/jni/libmediaplayer/decoder.cpp b/jni/libmediaplayer/decoder.cpp index 83df6a3..3c8a85a 100644 --- a/jni/libmediaplayer/decoder.cpp +++ b/jni/libmediaplayer/decoder.cpp @@ -5,28 +5,28 @@ IDecoder::IDecoder(AVStream* stream) { - mQueue = new PacketQueue(); - mStream = stream; + mQueue = new PacketQueue(); + mStream = stream; } IDecoder::~IDecoder() { - if(mRunning) + if(mRunning) { stop(); } - free(mQueue); - avcodec_close(mStream->codec); + free(mQueue); + avcodec_close(mStream->codec); } void IDecoder::enqueue(AVPacket* packet) { - mQueue->put(packet); + mQueue->put(packet); } int IDecoder::packets() { - return mQueue->size(); + return mQueue->size(); } void IDecoder::stop() @@ -34,20 +34,20 @@ void IDecoder::stop() mQueue->abort(); __android_log_print(ANDROID_LOG_INFO, TAG, "waiting on end of decoder thread"); int ret = -1; - if((ret = wait()) != 0) { + if((ret = join()) != 0) { __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel IDecoder: %i", ret); return; } } -void IDecoder::handleRun(void* ptr) +void IDecoder::run() { - if(!prepare()) + if(!prepare()) { - __android_log_print(ANDROID_LOG_INFO, TAG, "Couldn't prepare decoder"); + __android_log_print(ANDROID_LOG_INFO, TAG, "Couldn't prepare decoder"); return; } - decode(ptr); + decode(); } bool IDecoder::prepare() @@ -57,10 +57,10 @@ bool IDecoder::prepare() bool IDecoder::process(AVPacket *packet) { - return false; + return false; } -bool IDecoder::decode(void* ptr) +bool IDecoder::decode() { return false; } diff --git a/jni/libmediaplayer/decoder.h b/jni/libmediaplayer/decoder.h index 5916fc4..22ab40c 100644 --- a/jni/libmediaplayer/decoder.h +++ b/jni/libmediaplayer/decoder.h @@ -14,21 +14,22 @@ extern "C" { class IDecoder : public Thread { public: - IDecoder(AVStream* stream); - ~IDecoder(); + IDecoder(AVStream* stream); + ~IDecoder(); - void stop(); - void enqueue(AVPacket* packet); - int packets(); + void stop(); + void enqueue(AVPacket* packet); + int packets(); protected: - PacketQueue* mQueue; - AVStream* mStream; + PacketQueue* mQueue; + AVStream* mStream; - virtual bool prepare(); - virtual bool decode(void* ptr); - virtual bool process(AVPacket *packet); - void handleRun(void* ptr); + virtual bool prepare(); + virtual bool decode(); + virtual bool process(AVPacket *packet); + + virtual void run(); }; #endif //FFMPEG_DECODER_H diff --git a/jni/libmediaplayer/decoder_audio.cpp b/jni/libmediaplayer/decoder_audio.cpp index 3ec469a..2235811 100644 --- a/jni/libmediaplayer/decoder_audio.cpp +++ b/jni/libmediaplayer/decoder_audio.cpp @@ -32,7 +32,7 @@ bool DecoderAudio::process(AVPacket *packet) return true; } -bool DecoderAudio::decode(void* ptr) +bool DecoderAudio::decode() { AVPacket pPacket; diff --git a/jni/libmediaplayer/decoder_audio.h b/jni/libmediaplayer/decoder_audio.h index ace3652..b64c470 100644 --- a/jni/libmediaplayer/decoder_audio.h +++ b/jni/libmediaplayer/decoder_audio.h @@ -12,15 +12,15 @@ class DecoderAudio : public IDecoder ~DecoderAudio(); - AudioDecodingHandler onDecode; + AudioDecodingHandler onDecode; private: - int16_t* mSamples; - int mSamplesSize; + int16_t* mSamples; + int mSamplesSize; - bool prepare(); - bool decode(void* ptr); - bool process(AVPacket *packet); + bool prepare(); + bool decode(); + bool process(AVPacket *packet); }; #endif //FFMPEG_DECODER_AUDIO_H diff --git a/jni/libmediaplayer/decoder_video.cpp b/jni/libmediaplayer/decoder_video.cpp index 1bf3eb6..34f18ff 100644 --- a/jni/libmediaplayer/decoder_video.cpp +++ b/jni/libmediaplayer/decoder_video.cpp @@ -7,8 +7,8 @@ static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; DecoderVideo::DecoderVideo(AVStream* stream) : IDecoder(stream) { - mStream->codec->get_buffer = getBuffer; - mStream->codec->release_buffer = releaseBuffer; + mStream->codec->get_buffer = getBuffer; + mStream->codec->release_buffer = releaseBuffer; } DecoderVideo::~DecoderVideo() @@ -17,11 +17,11 @@ DecoderVideo::~DecoderVideo() bool DecoderVideo::prepare() { - mFrame = avcodec_alloc_frame(); - if (mFrame == NULL) { - return false; - } - return true; + mFrame = avcodec_alloc_frame(); + if (mFrame == NULL) { + return false; + } + return true; } double DecoderVideo::synchronize(AVFrame *src_frame, double pts) { @@ -75,11 +75,11 @@ bool DecoderVideo::process(AVPacket *packet) return false; } -bool DecoderVideo::decode(void* ptr) +bool DecoderVideo::decode() { - AVPacket pPacket; + AVPacket pPacket; - __android_log_print(ANDROID_LOG_INFO, TAG, "decoding video"); + __android_log_print(ANDROID_LOG_INFO, TAG, "decoding video"); while(mRunning) { diff --git a/jni/libmediaplayer/decoder_video.h b/jni/libmediaplayer/decoder_video.h index c7d742c..a853f64 100644 --- a/jni/libmediaplayer/decoder_video.h +++ b/jni/libmediaplayer/decoder_video.h @@ -11,18 +11,19 @@ class DecoderVideo : public IDecoder DecoderVideo(AVStream* stream); ~DecoderVideo(); - VideoDecodingHandler onDecode; + VideoDecodingHandler onDecode; private: - AVFrame* mFrame; - double mVideoClock; - - bool prepare(); - double synchronize(AVFrame *src_frame, double pts); - bool decode(void* ptr); - bool process(AVPacket *packet); - static int getBuffer(struct AVCodecContext *c, AVFrame *pic); - static void releaseBuffer(struct AVCodecContext *c, AVFrame *pic); + AVFrame* mFrame; + double mVideoClock; + + bool prepare(); + double synchronize(AVFrame *src_frame, double pts); + bool decode(); + bool process(AVPacket *packet); + + static int getBuffer(struct AVCodecContext *c, AVFrame *pic); + static void releaseBuffer(struct AVCodecContext *c, AVFrame *pic); }; #endif //FFMPEG_DECODER_AUDIO_H diff --git a/jni/libmediaplayer/mediaplayer.cpp b/jni/libmediaplayer/mediaplayer.cpp index e171682..57a4cb1 100644 --- a/jni/libmediaplayer/mediaplayer.cpp +++ b/jni/libmediaplayer/mediaplayer.cpp @@ -327,12 +327,12 @@ void MediaPlayer::decodeMovie(void* ptr) AVStream* stream_audio = mMovieFile->streams[mAudioStreamIndex]; mDecoderAudio = new DecoderAudio(stream_audio); mDecoderAudio->onDecode = decode; - mDecoderAudio->startAsync(); + mDecoderAudio->start(); AVStream* stream_video = mMovieFile->streams[mVideoStreamIndex]; mDecoderVideo = new DecoderVideo(stream_video); mDecoderVideo->onDecode = decode; - mDecoderVideo->startAsync(); + mDecoderVideo->start(); mCurrentState = MEDIA_PLAYER_STARTED; __android_log_print(ANDROID_LOG_INFO, TAG, "playing %ix%i", mVideoWidth, mVideoHeight); @@ -366,17 +366,17 @@ void MediaPlayer::decodeMovie(void* ptr) //waits on end of video thread __android_log_print(ANDROID_LOG_ERROR, TAG, "waiting on video thread"); int ret = -1; - if((ret = mDecoderVideo->wait()) != 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel video thread: %i", ret); + if((ret = mDecoderVideo->join()) != 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel video thread: %i", ret); } __android_log_print(ANDROID_LOG_ERROR, TAG, "waiting on audio thread"); - if((ret = mDecoderAudio->wait()) != 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel audio thread: %i", ret); + if((ret = mDecoderAudio->join()) != 0) { + __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel audio thread: %i", ret); } if(mCurrentState == MEDIA_PLAYER_STATE_ERROR) { - __android_log_print(ANDROID_LOG_INFO, TAG, "playing err"); + __android_log_print(ANDROID_LOG_INFO, TAG, "playing err"); } mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE; __android_log_print(ANDROID_LOG_INFO, TAG, "end of playing"); diff --git a/jni/libmediaplayer/mediaplayer.h b/jni/libmediaplayer/mediaplayer.h index 0da9881..3bb0df9 100644 --- a/jni/libmediaplayer/mediaplayer.h +++ b/jni/libmediaplayer/mediaplayer.h @@ -119,71 +119,73 @@ class MediaPlayer public: MediaPlayer(); ~MediaPlayer(); - status_t setDataSource(const char *url); - status_t setVideoSurface(JNIEnv* env, jobject jsurface); - status_t setListener(MediaPlayerListener *listener); - status_t start(); - status_t stop(); - status_t pause(); - bool isPlaying(); - status_t getVideoWidth(int *w); - status_t getVideoHeight(int *h); - status_t seekTo(int msec); - status_t getCurrentPosition(int *msec); - status_t getDuration(int *msec); - status_t reset(); - status_t setAudioStreamType(int type); - status_t prepare(); - void notify(int msg, int ext1, int ext2); + status_t setDataSource(const char *url); + status_t setVideoSurface(JNIEnv* env, jobject jsurface); + status_t setListener(MediaPlayerListener *listener); + status_t start(); + status_t stop(); + status_t pause(); + bool isPlaying(); + status_t getVideoWidth(int *w); + status_t getVideoHeight(int *h); + status_t seekTo(int msec); + status_t getCurrentPosition(int *msec); + status_t getDuration(int *msec); + status_t reset(); + status_t setAudioStreamType(int type); + status_t prepare(); + void notify(int msg, int ext1, int ext2); // static sp decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); // static sp decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat); // static int snoop(short *data, int len, int kind); // status_t invoke(const Parcel& request, Parcel *reply); // status_t setMetadataFilter(const Parcel& filter); // status_t getMetadata(bool update_only, bool apply_filter, Parcel *metadata); - status_t suspend(); - status_t resume(); + status_t suspend(); + status_t resume(); private: - status_t prepareAudio(); - status_t prepareVideo(); - bool shouldCancel(PacketQueue* queue); - static void ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl); - static void* startPlayer(void* ptr); + status_t prepareAudio(); + status_t prepareVideo(); + bool shouldCancel(PacketQueue* queue); + static void ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl); + static void* startPlayer(void* ptr); - static void decode(AVFrame* frame, double pts); - static void decode(int16_t* buffer, int buffer_size); + static void decode(AVFrame* frame, double pts); + static void decode(int16_t* buffer, int buffer_size); - void decodeMovie(void* ptr); + void decodeMovie(void* ptr); - double mTime; - pthread_mutex_t mLock; - pthread_t mPlayerThread; - PacketQueue* mVideoQueue; - //Mutex mNotifyLock; - //Condition mSignal; - MediaPlayerListener* mListener; - AVFormatContext* mMovieFile; - int mAudioStreamIndex; - int mVideoStreamIndex; - DecoderAudio* mDecoderAudio; - DecoderVideo* mDecoderVideo; - AVFrame* mFrame; - struct SwsContext* mConvertCtx; - - void* mCookie; - media_player_states mCurrentState; - int mDuration; - int mCurrentPosition; - int mSeekPosition; - bool mPrepareSync; - status_t mPrepareStatus; - int mStreamType; - bool mLoop; - float mLeftVolume; - float mRightVolume; - int mVideoWidth; - int mVideoHeight; + double mTime; + + pthread_mutex_t mLock; + pthread_t mPlayerThread; + PacketQueue* mVideoQueue; + //Mutex mNotifyLock; + //Condition mSignal; + + MediaPlayerListener* mListener; + AVFormatContext* mMovieFile; + int mAudioStreamIndex; + int mVideoStreamIndex; + DecoderAudio* mDecoderAudio; + DecoderVideo* mDecoderVideo; + AVFrame* mFrame; + struct SwsContext* mConvertCtx; + + void* mCookie; + media_player_states mCurrentState; + int mDuration; + int mCurrentPosition; + int mSeekPosition; + bool mPrepareSync; + status_t mPrepareStatus; + int mStreamType; + bool mLoop; + float mLeftVolume; + float mRightVolume; + int mVideoWidth; + int mVideoHeight; }; #endif // FFMPEG_MEDIAPLAYER_H diff --git a/jni/libmediaplayer/thread.cpp b/jni/libmediaplayer/thread.cpp index 4151bab..ff9825a 100644 --- a/jni/libmediaplayer/thread.cpp +++ b/jni/libmediaplayer/thread.cpp @@ -1,65 +1,81 @@ -#include +/* + * Copyright (c) 2011 Petr Havlena havlenapetr@gmail.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + #include "thread.h" -#define TAG "FFMpegThread" +#define THREAD_NAME_MAX 50 -Thread::Thread() -{ - pthread_mutex_init(&mLock, NULL); - pthread_cond_init(&mCondition, NULL); -} +#define LOG_TAG "Thread" -Thread::~Thread() -{ -} +static int sThreadCounter = 0; -void Thread::start() +Thread::Thread() : + mName(NULL) { - handleRun(NULL); + init(); } -void Thread::startAsync() +Thread::Thread(const char* name) : + mName(name) { - pthread_create(&mThread, NULL, startThread, this); + init(); } -int Thread::wait() +Thread::~Thread() { - if(!mRunning) - { - return 0; - } - return pthread_join(mThread, NULL); } -void Thread::stop() +void Thread::init() { + pthread_mutex_init(&mLock, NULL); + pthread_cond_init(&mCondition, NULL); + sThreadCounter++; } -void* Thread::startThread(void* ptr) +void Thread::start() { - __android_log_print(ANDROID_LOG_INFO, TAG, "starting thread"); - Thread* thread = (Thread *) ptr; - thread->mRunning = true; - thread->handleRun(ptr); - thread->mRunning = false; - __android_log_print(ANDROID_LOG_INFO, TAG, "thread ended"); + pthread_create(&mThread, NULL, handleThreadStart, this); } -void Thread::waitOnNotify() +int Thread::join() { - pthread_mutex_lock(&mLock); - pthread_cond_wait(&mCondition, &mLock); - pthread_mutex_unlock(&mLock); + if(!mRunning) + { + return 0; + } + return pthread_join(mThread, NULL); } -void Thread::notify() +void* Thread::handleThreadStart(void* ptr) { - pthread_mutex_lock(&mLock); - pthread_cond_signal(&mCondition); - pthread_mutex_unlock(&mLock); + Thread* thread = (Thread *) ptr; + + thread->mRunning = true; + + thread->run(); + + thread->mRunning = false; + + return 0; } -void Thread::handleRun(void* ptr) +void Thread::run() { } diff --git a/jni/libmediaplayer/thread.h b/jni/libmediaplayer/thread.h index b06d5f1..f6f0b20 100644 --- a/jni/libmediaplayer/thread.h +++ b/jni/libmediaplayer/thread.h @@ -1,33 +1,143 @@ -#ifndef FFMPEG_THREAD_H -#define FFMPEG_THREAD_H +/* + * Copyright (c) 2011 Petr Havlena havlenapetr@gmail.com + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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., + * 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +/* + * Copyright (C) 2007 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef _ENGINE_Thread_H_ +#define _ENGINE_Thread_H_ #include +#include + +/* + * Simple mutex class. The implementation is system-dependent. + * + * The mutex must be unlocked by the thread that locked it. They are not + * recursive, i.e. the same thread can't lock it multiple times. + */ +class Mutex { +public: + enum { + PRIVATE = 0, + SHARED = 1 + }; + + Mutex(); + Mutex(const char* name); + Mutex(int type, const char* name = NULL); + ~Mutex(); + + // lock or unlock the mutex + int lock(); + void unlock(); + + // lock if possible; returns 0 on success, error otherwise + int tryLock(); + + // Manages the mutex automatically. It'll be locked when Autolock is + // constructed and released when Autolock goes out of scope. + class gAutolock { + public: + inline gAutolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); } + inline gAutolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); } + inline ~gAutolock() { mLock.unlock(); } + private: + Mutex& mLock; + }; + +private: + + // A mutex cannot be copied + Mutex(const Mutex&); + Mutex& operator = (const Mutex&); + + pthread_mutex_t mMutex; +}; + +inline Mutex::Mutex() { + pthread_mutex_init(&mMutex, NULL); +} +inline Mutex::Mutex(const char* name) { + pthread_mutex_init(&mMutex, NULL); +} +inline Mutex::Mutex(int type, const char* name) { + if (type == SHARED) { + pthread_mutexattr_t attr; + pthread_mutexattr_init(&attr); + pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED); + pthread_mutex_init(&mMutex, &attr); + pthread_mutexattr_destroy(&attr); + } else { + pthread_mutex_init(&mMutex, NULL); + } +} +inline Mutex::~Mutex() { + pthread_mutex_destroy(&mMutex); +} +inline int Mutex::lock() { + return -pthread_mutex_lock(&mMutex); +} +inline void Mutex::unlock() { + pthread_mutex_unlock(&mMutex); +} +inline int Mutex::tryLock() { + return -pthread_mutex_trylock(&mMutex); +} class Thread { public: - Thread(); - ~Thread(); - - void start(); - void startAsync(); - int wait(); + Thread(); + Thread(const char* name); + ~Thread(); - void waitOnNotify(); - void notify(); - virtual void stop(); + void start(); + int join(); + + bool isRunning() { return mRunning; }; protected: - bool mRunning; + bool mRunning; + + virtual void run(); - virtual void handleRun(void* ptr); - private: - pthread_t mThread; - pthread_mutex_t mLock; - pthread_cond_t mCondition; + pthread_t mThread; + pthread_mutex_t mLock; + pthread_cond_t mCondition; + const char* mName; - static void* startThread(void* ptr); + void init(); + static void* handleThreadStart(void* ptr); }; -#endif //FFMPEG_DECODER_H +#endif // Thread_H From ab3d93a3c3b3a7223197799a84df47d30852fc48 Mon Sep 17 00:00:00 2001 From: havlenapetr Date: Wed, 25 May 2011 13:30:06 +0200 Subject: [PATCH 3/5] add default properties --- default.properties | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100755 default.properties diff --git a/default.properties b/default.properties new file mode 100755 index 0000000..d5eca74 --- /dev/null +++ b/default.properties @@ -0,0 +1,13 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system use, +# "build.properties", and override values to adapt the script to your +# project structure. + +# Project target. +target=android-3 +# Indicates whether an apk should be generated for each density. +split.density=false From 198b719ba8dcda05e125ed07f5bada884d1a1c1c Mon Sep 17 00:00:00 2001 From: havlenapetr Date: Wed, 25 May 2011 14:49:16 +0200 Subject: [PATCH 4/5] rework of mediaplayer decode loop --- jni/libmediaplayer/decoder_audio.cpp | 6 +- jni/libmediaplayer/decoder_audio.h | 11 +- jni/libmediaplayer/decoder_video.cpp | 8 +- jni/libmediaplayer/decoder_video.h | 11 +- jni/libmediaplayer/mediaplayer.cpp | 430 +++++++++++++++------------ jni/libmediaplayer/mediaplayer.h | 66 ++-- jni/libmediaplayer/thread.h | 8 +- 7 files changed, 310 insertions(+), 230 deletions(-) diff --git a/jni/libmediaplayer/decoder_audio.cpp b/jni/libmediaplayer/decoder_audio.cpp index 2235811..6954cf8 100644 --- a/jni/libmediaplayer/decoder_audio.cpp +++ b/jni/libmediaplayer/decoder_audio.cpp @@ -3,12 +3,14 @@ #define TAG "FFMpegAudioDecoder" -DecoderAudio::DecoderAudio(AVStream* stream) : IDecoder(stream) +DecoderAudio::DecoderAudio(AVStream* stream, DecoderAudioCallback* callback) : IDecoder(stream) { + mCallback = callback; } DecoderAudio::~DecoderAudio() { + delete mCallback; } bool DecoderAudio::prepare() @@ -27,7 +29,7 @@ bool DecoderAudio::process(AVPacket *packet) int len = avcodec_decode_audio3(mStream->codec, mSamples, &size, packet); //call handler for posting buffer to os audio driver - onDecode(mSamples, size); + mCallback->onDecode(mSamples, size); return true; } diff --git a/jni/libmediaplayer/decoder_audio.h b/jni/libmediaplayer/decoder_audio.h index b64c470..1efd3f6 100644 --- a/jni/libmediaplayer/decoder_audio.h +++ b/jni/libmediaplayer/decoder_audio.h @@ -3,20 +3,23 @@ #include "decoder.h" -typedef void (*AudioDecodingHandler) (int16_t*,int); +class DecoderAudioCallback +{ +public: + virtual void onDecode(int16_t* buffer, int buffer_size); +}; class DecoderAudio : public IDecoder { public: - DecoderAudio(AVStream* stream); - + DecoderAudio(AVStream* stream, DecoderAudioCallback* callback); ~DecoderAudio(); - AudioDecodingHandler onDecode; private: int16_t* mSamples; int mSamplesSize; + DecoderAudioCallback* mCallback; bool prepare(); bool decode(); diff --git a/jni/libmediaplayer/decoder_video.cpp b/jni/libmediaplayer/decoder_video.cpp index 34f18ff..f76f675 100644 --- a/jni/libmediaplayer/decoder_video.cpp +++ b/jni/libmediaplayer/decoder_video.cpp @@ -5,14 +5,16 @@ static uint64_t global_video_pkt_pts = AV_NOPTS_VALUE; -DecoderVideo::DecoderVideo(AVStream* stream) : IDecoder(stream) +DecoderVideo::DecoderVideo(AVStream* stream, DecoderVideoCallback* callback) : IDecoder(stream) { + mCallback = callback; mStream->codec->get_buffer = getBuffer; mStream->codec->release_buffer = releaseBuffer; } DecoderVideo::~DecoderVideo() { + delete mCallback; } bool DecoderVideo::prepare() @@ -67,9 +69,7 @@ bool DecoderVideo::process(AVPacket *packet) if (completed) { pts = synchronize(mFrame, pts); - - onDecode(mFrame, pts); - + mCallback->onDecode(mFrame, pts); return true; } return false; diff --git a/jni/libmediaplayer/decoder_video.h b/jni/libmediaplayer/decoder_video.h index a853f64..aec15a6 100644 --- a/jni/libmediaplayer/decoder_video.h +++ b/jni/libmediaplayer/decoder_video.h @@ -3,19 +3,22 @@ #include "decoder.h" -typedef void (*VideoDecodingHandler) (AVFrame*,double); +class DecoderVideoCallback +{ +public: + virtual void onDecode(AVFrame* frame, double pts); +}; class DecoderVideo : public IDecoder { public: - DecoderVideo(AVStream* stream); + DecoderVideo(AVStream* stream, DecoderVideoCallback* callback); ~DecoderVideo(); - VideoDecodingHandler onDecode; - private: AVFrame* mFrame; double mVideoClock; + DecoderVideoCallback* mCallback; bool prepare(); double synchronize(AVFrame *src_frame, double pts); diff --git a/jni/libmediaplayer/mediaplayer.cpp b/jni/libmediaplayer/mediaplayer.cpp index 57a4cb1..91a1b7a 100644 --- a/jni/libmediaplayer/mediaplayer.cpp +++ b/jni/libmediaplayer/mediaplayer.cpp @@ -2,9 +2,6 @@ * mediaplayer.cpp */ -//#define LOG_NDEBUG 0 -#define TAG "FFMpegMediaPlayer" - #include #include #include @@ -22,15 +19,178 @@ extern "C" { #include +#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,LOG_TAG,__VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__) + #include "mediaplayer.h" #include "output.h" #define FPS_DEBUGGING false +#define LOG_TAG "FFMpegMediaPlayer" + static MediaPlayer* sPlayer; +class VideoCallback : public DecoderVideoCallback +{ +public: + virtual void onDecode(AVFrame* frame, double pts); +}; + +void VideoCallback::onDecode(AVFrame* frame, double pts) +{ + if(FPS_DEBUGGING) + { + timeval pTime; + static int frames = 0; + static double t1 = -1; + static double t2 = -1; + + gettimeofday(&pTime, NULL); + t2 = pTime.tv_sec + (pTime.tv_usec / 1000000.0); + if (t1 == -1 || t2 > t1 + 1) { + LOGI("Video fps:%i", frames); + //sPlayer->notify(MEDIA_INFO_FRAMERATE_VIDEO, frames, -1); + t1 = t2; + frames = 0; + } + frames++; + } + + // Convert the image from its native format to RGB + sws_scale(sPlayer->mConvertCtx, + frame->data, + frame->linesize, + 0, + sPlayer->mVideoHeight, + sPlayer->mFrame->data, + sPlayer->mFrame->linesize); + + Output::VideoDriver_updateSurface(); +} + +class AudioCallback : public DecoderAudioCallback +{ +public: + virtual void onDecode(int16_t* buffer, int buffer_size); +}; + +void AudioCallback::onDecode(int16_t* buffer, int buffer_size) +{ + if(FPS_DEBUGGING) + { + timeval pTime; + static int frames = 0; + static double t1 = -1; + static double t2 = -1; + + gettimeofday(&pTime, NULL); + t2 = pTime.tv_sec + (pTime.tv_usec / 1000000.0); + if (t1 == -1 || t2 > t1 + 1) { + LOGI("Video fps:%i", frames); + //sPlayer->notify(MEDIA_INFO_FRAMERATE_VIDEO, frames, -1); + t1 = t2; + frames = 0; + } + frames++; + } + + if(Output::AudioDriver_write(buffer, buffer_size) <= 0) { + LOGE("Couldn't write samples to audio track"); + } +} + +DecodeLoop::DecodeLoop(AVFormatContext* context, int audioStreamId, int videoStreamId, DecodeLoopCallback* callback) +{ + mCallback = callback; + mContext = context; + mAudioStreamId = audioStreamId; + mVideoStreamId = videoStreamId; + mEnding = false; +} + +DecodeLoop::~DecodeLoop() +{ + LOGI("killing decode loop"); + + mEnding = true; + + if(mDecoderAudio != NULL) { + mDecoderAudio->stop(); + } + if(mDecoderVideo != NULL) { + mDecoderVideo->stop(); + } + + if(join() != 0) { + LOGE("Couldn't cancel player thread"); + } + + // Close the codec + delete mDecoderAudio; + delete mDecoderVideo; +} + +void DecodeLoop::run() +{ + AVPacket pPacket; + + AVStream* stream_audio = mContext->streams[mAudioStreamId]; + mDecoderAudio = new DecoderAudio(stream_audio, new AudioCallback()); + mDecoderAudio->start(); + + AVStream* stream_video = mContext->streams[mVideoStreamId]; + mDecoderVideo = new DecoderVideo(stream_video, new VideoCallback()); + mDecoderVideo->start(); + + LOGI("playing"); + + while (!mEnding) + { + if (mDecoderVideo->packets() > FFMPEG_PLAYER_MAX_QUEUE_SIZE && + mDecoderAudio->packets() > FFMPEG_PLAYER_MAX_QUEUE_SIZE) { + usleep(200); + continue; + } + + + if(av_read_frame(mContext, &pPacket) < 0) { + mEnding = true; + continue; + } + + // Is this a packet from the video stream? + if (pPacket.stream_index == mVideoStreamId) { + mDecoderVideo->enqueue(&pPacket); + } + else if (pPacket.stream_index == mAudioStreamId) { + mDecoderAudio->enqueue(&pPacket); + } + else { + // Free the packet that was allocated by av_read_frame + av_free_packet(&pPacket); + } + } + + //waits on end of video thread + LOGI("waiting on video thread"); + int ret = -1; + if((ret = mDecoderVideo->join()) != 0) { + LOGE("Couldn't cancel video thread: %i", ret); + } + + LOGI("waiting on audio thread"); + if((ret = mDecoderAudio->join()) != 0) { + LOGE("Couldn't cancel audio thread: %i", ret); + } + + mCallback->onCompleted(); +} + + MediaPlayer::MediaPlayer() { + mDecodingLoop = NULL; mListener = NULL; mCookie = NULL; mDuration = -1; @@ -41,7 +201,6 @@ MediaPlayer::MediaPlayer() mPrepareSync = false; mPrepareStatus = NO_ERROR; mLoop = false; - pthread_mutex_init(&mLock, NULL); mLeftVolume = mRightVolume = 1.0; mVideoWidth = mVideoHeight = 0; sPlayer = this; @@ -56,7 +215,7 @@ MediaPlayer::~MediaPlayer() status_t MediaPlayer::prepareAudio() { - __android_log_print(ANDROID_LOG_INFO, TAG, "prepareAudio"); + LOGI("prepareAudio"); mAudioStreamIndex = -1; for (int i = 0; i < mMovieFile->nb_streams; i++) { if (mMovieFile->streams[i]->codec->codec_type == CODEC_TYPE_AUDIO) { @@ -100,7 +259,7 @@ status_t MediaPlayer::prepareAudio() status_t MediaPlayer::prepareVideo() { - __android_log_print(ANDROID_LOG_INFO, TAG, "prepareVideo"); + LOGI("prepareVideo"); // Find the first video stream mVideoStreamIndex = -1; for (int i = 0; i < mMovieFile->nb_streams; i++) { @@ -188,14 +347,14 @@ status_t MediaPlayer::prepare() status_t MediaPlayer::setListener(MediaPlayerListener* listener) { - __android_log_print(ANDROID_LOG_INFO, TAG, "setListener"); + LOGI("setListener"); mListener = listener; return NO_ERROR; } status_t MediaPlayer::setDataSource(const char *url) { - __android_log_print(ANDROID_LOG_INFO, TAG, "setDataSource(%s)", url); + LOGI("setDataSource(%s)", url); status_t err = BAD_VALUE; // Open video file if(av_open_input_file(&mMovieFile, url, NULL, 0, NULL) != 0) { @@ -210,40 +369,38 @@ status_t MediaPlayer::setDataSource(const char *url) } status_t MediaPlayer::suspend() { - __android_log_print(ANDROID_LOG_INFO, TAG, "suspend"); - - mCurrentState = MEDIA_PLAYER_STOPPED; - if(mDecoderAudio != NULL) { - mDecoderAudio->stop(); - } - if(mDecoderVideo != NULL) { - mDecoderVideo->stop(); - } - - if(pthread_join(mPlayerThread, NULL) != 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel player thread"); - } + LOGI("suspend"); + + mCurrentState = MEDIA_PLAYER_STOPPED; - // Close the codec - free(mDecoderAudio); - free(mDecoderVideo); + delete mDecodingLoop; + mDecodingLoop = NULL; - // Close the video file - av_close_input_file(mMovieFile); - - //close OS drivers - Output::AudioDriver_unregister(); - Output::VideoDriver_unregister(); + // Close the video file + av_close_input_file(mMovieFile); - __android_log_print(ANDROID_LOG_ERROR, TAG, "suspended"); + //close OS drivers + Output::AudioDriver_unregister(); + Output::VideoDriver_unregister(); + LOGE("suspended"); return NO_ERROR; } +void MediaPlayer::onCompleted() +{ + if(mCurrentState == MEDIA_PLAYER_STATE_ERROR) { + LOGI("playing err"); + } + + mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE; + LOGI("end of playing"); +} + status_t MediaPlayer::resume() { - //pthread_mutex_lock(&mLock); - mCurrentState = MEDIA_PLAYER_STARTED; - //pthread_mutex_unlock(&mLock); + Mutex::AutoLock _l(&mLock); + + mCurrentState = MEDIA_PLAYER_STARTED; return NO_ERROR; } @@ -265,166 +422,51 @@ bool MediaPlayer::shouldCancel(PacketQueue* queue) && queue->size() == 0)); } -void MediaPlayer::decode(AVFrame* frame, double pts) -{ - if(FPS_DEBUGGING) { - timeval pTime; - static int frames = 0; - static double t1 = -1; - static double t2 = -1; - - gettimeofday(&pTime, NULL); - t2 = pTime.tv_sec + (pTime.tv_usec / 1000000.0); - if (t1 == -1 || t2 > t1 + 1) { - __android_log_print(ANDROID_LOG_INFO, TAG, "Video fps:%i", frames); - //sPlayer->notify(MEDIA_INFO_FRAMERATE_VIDEO, frames, -1); - t1 = t2; - frames = 0; - } - frames++; - } - - // Convert the image from its native format to RGB - sws_scale(sPlayer->mConvertCtx, - frame->data, - frame->linesize, - 0, - sPlayer->mVideoHeight, - sPlayer->mFrame->data, - sPlayer->mFrame->linesize); - - Output::VideoDriver_updateSurface(); -} - -void MediaPlayer::decode(int16_t* buffer, int buffer_size) +status_t MediaPlayer::start() { - if(FPS_DEBUGGING) { - timeval pTime; - static int frames = 0; - static double t1 = -1; - static double t2 = -1; - - gettimeofday(&pTime, NULL); - t2 = pTime.tv_sec + (pTime.tv_usec / 1000000.0); - if (t1 == -1 || t2 > t1 + 1) { - __android_log_print(ANDROID_LOG_INFO, TAG, "Audio fps:%i", frames); - //sPlayer->notify(MEDIA_INFO_FRAMERATE_AUDIO, frames, -1); - t1 = t2; - frames = 0; - } - frames++; - } + Mutex::AutoLock _l(&mLock); - if(Output::AudioDriver_write(buffer, buffer_size) <= 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't write samples to audio track"); - } -} + if (mDecodingLoop != NULL || mCurrentState != MEDIA_PLAYER_PREPARED) { + return INVALID_OPERATION; + } -void MediaPlayer::decodeMovie(void* ptr) -{ - AVPacket pPacket; - - AVStream* stream_audio = mMovieFile->streams[mAudioStreamIndex]; - mDecoderAudio = new DecoderAudio(stream_audio); - mDecoderAudio->onDecode = decode; - mDecoderAudio->start(); - - AVStream* stream_video = mMovieFile->streams[mVideoStreamIndex]; - mDecoderVideo = new DecoderVideo(stream_video); - mDecoderVideo->onDecode = decode; - mDecoderVideo->start(); - - mCurrentState = MEDIA_PLAYER_STARTED; - __android_log_print(ANDROID_LOG_INFO, TAG, "playing %ix%i", mVideoWidth, mVideoHeight); - while (mCurrentState != MEDIA_PLAYER_DECODED && mCurrentState != MEDIA_PLAYER_STOPPED && - mCurrentState != MEDIA_PLAYER_STATE_ERROR) - { - if (mDecoderVideo->packets() > FFMPEG_PLAYER_MAX_QUEUE_SIZE && - mDecoderAudio->packets() > FFMPEG_PLAYER_MAX_QUEUE_SIZE) { - usleep(200); - continue; - } - - if(av_read_frame(mMovieFile, &pPacket) < 0) { - mCurrentState = MEDIA_PLAYER_DECODED; - continue; - } - - // Is this a packet from the video stream? - if (pPacket.stream_index == mVideoStreamIndex) { - mDecoderVideo->enqueue(&pPacket); - } - else if (pPacket.stream_index == mAudioStreamIndex) { - mDecoderAudio->enqueue(&pPacket); - } - else { - // Free the packet that was allocated by av_read_frame - av_free_packet(&pPacket); - } - } - - //waits on end of video thread - __android_log_print(ANDROID_LOG_ERROR, TAG, "waiting on video thread"); - int ret = -1; - if((ret = mDecoderVideo->join()) != 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel video thread: %i", ret); - } - - __android_log_print(ANDROID_LOG_ERROR, TAG, "waiting on audio thread"); - if((ret = mDecoderAudio->join()) != 0) { - __android_log_print(ANDROID_LOG_ERROR, TAG, "Couldn't cancel audio thread: %i", ret); - } - - if(mCurrentState == MEDIA_PLAYER_STATE_ERROR) { - __android_log_print(ANDROID_LOG_INFO, TAG, "playing err"); - } - mCurrentState = MEDIA_PLAYER_PLAYBACK_COMPLETE; - __android_log_print(ANDROID_LOG_INFO, TAG, "end of playing"); -} - -void* MediaPlayer::startPlayer(void* ptr) -{ - __android_log_print(ANDROID_LOG_INFO, TAG, "starting main player thread"); - sPlayer->decodeMovie(ptr); -} + mDecodingLoop = new DecodeLoop(mMovieFile, mAudioStreamIndex, mVideoStreamIndex, this); + mDecodingLoop->start(); -status_t MediaPlayer::start() -{ - if (mCurrentState != MEDIA_PLAYER_PREPARED) { - return INVALID_OPERATION; - } - pthread_create(&mPlayerThread, NULL, startPlayer, NULL); - return NO_ERROR; + return NO_ERROR; } status_t MediaPlayer::stop() { - //pthread_mutex_lock(&mLock); - mCurrentState = MEDIA_PLAYER_STOPPED; - //pthread_mutex_unlock(&mLock); + Mutex::AutoLock _l(&mLock); + + mCurrentState = MEDIA_PLAYER_STOPPED; return NO_ERROR; } status_t MediaPlayer::pause() { - //pthread_mutex_lock(&mLock); - mCurrentState = MEDIA_PLAYER_PAUSED; - //pthread_mutex_unlock(&mLock); - return NO_ERROR; + Mutex::AutoLock _l(&mLock); + + mCurrentState = MEDIA_PLAYER_PAUSED; + return NO_ERROR; } bool MediaPlayer::isPlaying() { + Mutex::AutoLock _l(&mLock); return mCurrentState == MEDIA_PLAYER_STARTED || mCurrentState == MEDIA_PLAYER_DECODED; } status_t MediaPlayer::getVideoWidth(int *w) { - if (mCurrentState < MEDIA_PLAYER_PREPARED) { - return INVALID_OPERATION; - } - *w = mVideoWidth; + Mutex::AutoLock _l(&mLock); + + if (mCurrentState < MEDIA_PLAYER_PREPARED) { + return INVALID_OPERATION; + } + *w = mVideoWidth; return NO_ERROR; } @@ -439,36 +481,42 @@ status_t MediaPlayer::getVideoHeight(int *h) status_t MediaPlayer::getCurrentPosition(int *msec) { - if (mCurrentState < MEDIA_PLAYER_PREPARED) { - return INVALID_OPERATION; - } - *msec = 0/*av_gettime()*/; - //__android_log_print(ANDROID_LOG_INFO, TAG, "position %i", *msec); - return NO_ERROR; + Mutex::AutoLock _l(&mLock); + + if (mCurrentState < MEDIA_PLAYER_PREPARED) { + return INVALID_OPERATION; + } + *msec = 0/*av_gettime()*/; + //LOGI("position %i", *msec); + return NO_ERROR; } status_t MediaPlayer::getDuration(int *msec) { - if (mCurrentState < MEDIA_PLAYER_PREPARED) { - return INVALID_OPERATION; - } - *msec = mDuration / 1000; + Mutex::AutoLock _l(&mLock); + + if (mCurrentState < MEDIA_PLAYER_PREPARED) { + return INVALID_OPERATION; + } + *msec = mDuration / 1000; return NO_ERROR; } status_t MediaPlayer::seekTo(int msec) { + Mutex::AutoLock _l(&mLock); return INVALID_OPERATION; } status_t MediaPlayer::reset() { + Mutex::AutoLock _l(&mLock); return INVALID_OPERATION; } status_t MediaPlayer::setAudioStreamType(int type) { - return NO_ERROR; + return NO_ERROR; } void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl) { @@ -478,7 +526,7 @@ void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl * Something went really wrong and we will crash now. */ case AV_LOG_PANIC: - __android_log_print(ANDROID_LOG_ERROR, TAG, "AV_LOG_PANIC: %s", fmt); + LOGE("AV_LOG_PANIC: %s", fmt); //sPlayer->mCurrentState = MEDIA_PLAYER_STATE_ERROR; break; @@ -488,7 +536,7 @@ void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl * on headers or an illegal combination of parameters is used. */ case AV_LOG_FATAL: - __android_log_print(ANDROID_LOG_ERROR, TAG, "AV_LOG_FATAL: %s", fmt); + LOGE("AV_LOG_FATAL: %s", fmt); //sPlayer->mCurrentState = MEDIA_PLAYER_STATE_ERROR; break; @@ -497,7 +545,7 @@ void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl * However, not all future data is affected. */ case AV_LOG_ERROR: - __android_log_print(ANDROID_LOG_ERROR, TAG, "AV_LOG_ERROR: %s", fmt); + LOGE("AV_LOG_ERROR: %s", fmt); //sPlayer->mCurrentState = MEDIA_PLAYER_STATE_ERROR; break; @@ -506,15 +554,15 @@ void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl * lead to problems. An example would be the use of '-vstrict -2'. */ case AV_LOG_WARNING: - __android_log_print(ANDROID_LOG_ERROR, TAG, "AV_LOG_WARNING: %s", fmt); + LOGE("AV_LOG_WARNING: %s", fmt); break; case AV_LOG_INFO: - __android_log_print(ANDROID_LOG_INFO, TAG, "%s", fmt); + LOGI("%s", fmt); break; case AV_LOG_DEBUG: - __android_log_print(ANDROID_LOG_DEBUG, TAG, "%s", fmt); + LOGI("%s", fmt); break; } @@ -522,13 +570,13 @@ void MediaPlayer::ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl void MediaPlayer::notify(int msg, int ext1, int ext2) { - //__android_log_print(ANDROID_LOG_INFO, TAG, "message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2); + //LOGI("message received msg=%d, ext1=%d, ext2=%d", msg, ext1, ext2); bool send = true; bool locked = false; if ((mListener != 0) && send) { - //__android_log_print(ANDROID_LOG_INFO, TAG, "callback application"); + //LOGI("callback application"); mListener->notify(msg, ext1, ext2); - //__android_log_print(ANDROID_LOG_INFO, TAG, "back from callback"); + //LOGI("back from callback"); } } diff --git a/jni/libmediaplayer/mediaplayer.h b/jni/libmediaplayer/mediaplayer.h index 3bb0df9..1365115 100644 --- a/jni/libmediaplayer/mediaplayer.h +++ b/jni/libmediaplayer/mediaplayer.h @@ -1,8 +1,6 @@ #ifndef FFMPEG_MEDIAPLAYER_H #define FFMPEG_MEDIAPLAYER_H -#include - #include #include @@ -106,6 +104,34 @@ enum media_player_states { MEDIA_PLAYER_PLAYBACK_COMPLETE = 1 << 8 }; +class DecodeLoopCallback +{ +private: + virtual void onCompleted(); + + friend class DecodeLoop; +}; + +class DecodeLoop : public Thread +{ +private: + DecoderAudio* mDecoderAudio; + int mAudioStreamId; + DecoderVideo* mDecoderVideo; + int mVideoStreamId; + AVFormatContext* mContext; + + DecodeLoopCallback* mCallback; + bool mEnding; + +public: + DecodeLoop(AVFormatContext* context, int audioStreamId, int videoStreamId, DecodeLoopCallback* callback); + ~DecodeLoop(); + + virtual void run(); + void suspend(); +}; + // ---------------------------------------------------------------------------- // ref-counted object for callbacks class MediaPlayerListener @@ -114,7 +140,7 @@ class MediaPlayerListener virtual void notify(int msg, int ext1, int ext2) = 0; }; -class MediaPlayer +class MediaPlayer : private DecodeLoopCallback { public: MediaPlayer(); @@ -149,29 +175,20 @@ class MediaPlayer status_t prepareVideo(); bool shouldCancel(PacketQueue* queue); static void ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl); - static void* startPlayer(void* ptr); - - static void decode(AVFrame* frame, double pts); - static void decode(int16_t* buffer, int buffer_size); - - void decodeMovie(void* ptr); - double mTime; + double mTime; - pthread_mutex_t mLock; - pthread_t mPlayerThread; - PacketQueue* mVideoQueue; + PacketQueue* mVideoQueue; + Mutex mLock; //Mutex mNotifyLock; //Condition mSignal; - MediaPlayerListener* mListener; - AVFormatContext* mMovieFile; - int mAudioStreamIndex; - int mVideoStreamIndex; - DecoderAudio* mDecoderAudio; - DecoderVideo* mDecoderVideo; - AVFrame* mFrame; - struct SwsContext* mConvertCtx; + MediaPlayerListener* mListener; + AVFormatContext* mMovieFile; + int mAudioStreamIndex; + int mVideoStreamIndex; + + DecodeLoop* mDecodingLoop; void* mCookie; media_player_states mCurrentState; @@ -184,8 +201,15 @@ class MediaPlayer bool mLoop; float mLeftVolume; float mRightVolume; + +public: + struct SwsContext* mConvertCtx; + AVFrame* mFrame; int mVideoWidth; int mVideoHeight; + +protected: + virtual void onCompleted(); }; #endif // FFMPEG_MEDIAPLAYER_H diff --git a/jni/libmediaplayer/thread.h b/jni/libmediaplayer/thread.h index f6f0b20..1893fc6 100644 --- a/jni/libmediaplayer/thread.h +++ b/jni/libmediaplayer/thread.h @@ -65,11 +65,11 @@ class Mutex { // Manages the mutex automatically. It'll be locked when Autolock is // constructed and released when Autolock goes out of scope. - class gAutolock { + class AutoLock { public: - inline gAutolock(Mutex& mutex) : mLock(mutex) { mLock.lock(); } - inline gAutolock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); } - inline ~gAutolock() { mLock.unlock(); } + inline AutoLock(Mutex& mutex) : mLock(mutex) { mLock.lock(); } + inline AutoLock(Mutex* mutex) : mLock(*mutex) { mLock.lock(); } + inline ~AutoLock() { mLock.unlock(); } private: Mutex& mLock; }; From e95e721ae016eab5ec4ceaaf29010b881ed8bac7 Mon Sep 17 00:00:00 2001 From: havlenapetr Date: Tue, 31 May 2011 13:22:16 +0200 Subject: [PATCH 5/5] add autoscale option into player --- AndroidManifest.xml | 2 +- default.properties | 2 +- jni/include/android/surface.h | 17 +-- jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp | 24 ++++ jni/libmediaplayer/mediaplayer.cpp | 131 +++++++++--------- jni/libmediaplayer/mediaplayer.h | 47 ++++--- jni/libmediaplayer/output.cpp | 6 +- jni/libmediaplayer/output.h | 2 +- jni/prebuilt/libjnivideo.so | Bin 108501 -> 107960 bytes src/com/media/ffmpeg/FFMpegPlayer.java | 14 +- .../android/FFMpegMovieViewAndroid.java | 13 +- .../ffmpeg/ui/FFMpegPlayerActivity.java | 25 ++++ 12 files changed, 179 insertions(+), 104 deletions(-) diff --git a/AndroidManifest.xml b/AndroidManifest.xml index c60c937..155a497 100755 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -25,7 +25,7 @@ - + \ No newline at end of file diff --git a/default.properties b/default.properties index d5eca74..9d6f70d 100755 --- a/default.properties +++ b/default.properties @@ -8,6 +8,6 @@ # project structure. # Project target. -target=android-3 +target=android-4 # Indicates whether an apk should be generated for each density. split.density=false diff --git a/jni/include/android/surface.h b/jni/include/android/surface.h index f45c81e..dd017fc 100644 --- a/jni/include/android/surface.h +++ b/jni/include/android/surface.h @@ -1,4 +1,5 @@ /* + * Copyright (C) 2011 Petr Havlena havlenapetr@gmail.com * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,13 +21,13 @@ #include #include -#define ANDROID_SURFACE_RESULT_SUCCESS 0 -#define ANDROID_SURFACE_RESULT_NOT_VALID -1 -#define ANDROID_SURFACE_RESULT_COULDNT_LOCK -2 -#define ANDROID_SURFACE_RESULT_COULDNT_UNLOCK_AND_POST -3 -#define ANDROID_SURFACE_RESULT_COULDNT_INIT_BITMAP_SURFACE -4 -#define ANDROID_SURFACE_RESULT_COULDNT_INIT_BITMAP_CLIENT -5 -#define ANDROID_SURFACE_RESULT_JNI_EXCEPTION -6 +#define ANDROID_SURFACE_RESULT_SUCCESS 0 +#define ANDROID_SURFACE_RESULT_NOT_VALID -1 +#define ANDROID_SURFACE_RESULT_COULDNT_LOCK -2 +#define ANDROID_SURFACE_RESULT_COULDNT_UNLOCK_AND_POST -3 +#define ANDROID_SURFACE_RESULT_COULDNT_INIT_BITMAP_SURFACE -4 +#define ANDROID_SURFACE_RESULT_COULDNT_INIT_BITMAP_CLIENT -5 +#define ANDROID_SURFACE_RESULT_JNI_EXCEPTION -6 #ifdef __cplusplus extern "C" { @@ -36,7 +37,7 @@ int AndroidSurface_register(JNIEnv* env, jobject jsurface); int AndroidSurface_getPixels(int width, int height, void** pixels); -int AndroidSurface_updateSurface(); +int AndroidSurface_updateSurface(bool autoscale = true); int AndroidSurface_unregister(); diff --git a/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp b/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp index 7963b2a..5e2b20f 100644 --- a/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp +++ b/jni/jni/com_media_ffmpeg_FFMpegPlayer.cpp @@ -220,6 +220,28 @@ com_media_ffmpeg_FFMpegPlayer_pause(JNIEnv *env, jobject thiz) process_media_player_call( env, thiz, mp->pause(), NULL, NULL ); } +static void +com_media_ffmpeg_FFMpegPlayer_setResolution(JNIEnv *env, jobject thiz, jint width, jint height) +{ + MediaPlayer* mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setResolution(width, height), NULL, NULL ); +} + +static void +com_media_ffmpeg_FFMpegPlayer_setAutoscale(JNIEnv *env, jobject thiz, jboolean value) +{ + MediaPlayer* mp = getMediaPlayer(env, thiz); + if (mp == NULL ) { + jniThrowException(env, "java/lang/IllegalStateException", NULL); + return; + } + process_media_player_call( env, thiz, mp->setAutoscale(value), NULL, NULL ); +} + static jboolean com_media_ffmpeg_FFMpegPlayer_isPlaying(JNIEnv *env, jobject thiz) { @@ -406,6 +428,8 @@ static JNINativeMethod gMethods[] = { {"prepare", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_prepare}, {"_start", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_start}, {"_stop", "()V", (void *)com_media_ffmpeg_FFMpegPlayer_stop}, + {"_setResolution", "(II)V", (void *)com_media_ffmpeg_FFMpegPlayer_setResolution}, + {"_setAutoscale", "(Z)V", (void *)com_media_ffmpeg_FFMpegPlayer_setAutoscale}, {"getVideoWidth", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoWidth}, {"getVideoHeight", "()I", (void *)com_media_ffmpeg_FFMpegPlayer_getVideoHeight}, {"seekTo", "(I)V", (void *)com_media_ffmpeg_FFMpegPlayer_seekTo}, diff --git a/jni/libmediaplayer/mediaplayer.cpp b/jni/libmediaplayer/mediaplayer.cpp index 91a1b7a..1e2d72d 100644 --- a/jni/libmediaplayer/mediaplayer.cpp +++ b/jni/libmediaplayer/mediaplayer.cpp @@ -25,8 +25,6 @@ extern "C" { #include "mediaplayer.h" #include "output.h" -#define FPS_DEBUGGING false - #define LOG_TAG "FFMpegMediaPlayer" static MediaPlayer* sPlayer; @@ -39,34 +37,16 @@ class VideoCallback : public DecoderVideoCallback void VideoCallback::onDecode(AVFrame* frame, double pts) { - if(FPS_DEBUGGING) - { - timeval pTime; - static int frames = 0; - static double t1 = -1; - static double t2 = -1; - - gettimeofday(&pTime, NULL); - t2 = pTime.tv_sec + (pTime.tv_usec / 1000000.0); - if (t1 == -1 || t2 > t1 + 1) { - LOGI("Video fps:%i", frames); - //sPlayer->notify(MEDIA_INFO_FRAMERATE_VIDEO, frames, -1); - t1 = t2; - frames = 0; - } - frames++; - } - // Convert the image from its native format to RGB sws_scale(sPlayer->mConvertCtx, frame->data, frame->linesize, - 0, + 0, sPlayer->mVideoHeight, sPlayer->mFrame->data, - sPlayer->mFrame->linesize); + sPlayer->mFrame->linesize); - Output::VideoDriver_updateSurface(); + Output::VideoDriver_updateSurface(sPlayer->mAutoscale); } class AudioCallback : public DecoderAudioCallback @@ -77,24 +57,6 @@ class AudioCallback : public DecoderAudioCallback void AudioCallback::onDecode(int16_t* buffer, int buffer_size) { - if(FPS_DEBUGGING) - { - timeval pTime; - static int frames = 0; - static double t1 = -1; - static double t2 = -1; - - gettimeofday(&pTime, NULL); - t2 = pTime.tv_sec + (pTime.tv_usec / 1000000.0); - if (t1 == -1 || t2 > t1 + 1) { - LOGI("Video fps:%i", frames); - //sPlayer->notify(MEDIA_INFO_FRAMERATE_VIDEO, frames, -1); - t1 = t2; - frames = 0; - } - frames++; - } - if(Output::AudioDriver_write(buffer, buffer_size) <= 0) { LOGE("Couldn't write samples to audio track"); } @@ -152,8 +114,14 @@ void DecodeLoop::run() usleep(200); continue; } + + if(mPaused) { + usleep(200); + continue; + } - + sPlayer->mLock.lock(); + if(av_read_frame(mContext, &pPacket) < 0) { mEnding = true; continue; @@ -170,6 +138,8 @@ void DecodeLoop::run() // Free the packet that was allocated by av_read_frame av_free_packet(&pPacket); } + + sPlayer->mLock.unlock(); } //waits on end of video thread @@ -201,6 +171,7 @@ MediaPlayer::MediaPlayer() mPrepareSync = false; mPrepareStatus = NO_ERROR; mLoop = false; + mAutoscale = true; mLeftVolume = mRightVolume = 1.0; mVideoWidth = mVideoHeight = 0; sPlayer = this; @@ -290,11 +261,11 @@ status_t MediaPlayer::prepareVideo() mVideoHeight = codec_ctx->height; mDuration = mMovieFile->duration; - mConvertCtx = sws_getContext(stream->codec->width, - stream->codec->height, + mConvertCtx = sws_getContext(mVideoWidth, + mVideoHeight, stream->codec->pix_fmt, - stream->codec->width, - stream->codec->height, + mVideoWidth, + mVideoHeight, PIX_FMT_RGB565, SWS_POINT, NULL, @@ -305,27 +276,37 @@ status_t MediaPlayer::prepareVideo() return INVALID_OPERATION; } - void* pixels; - if (Output::VideoDriver_getPixels(stream->codec->width, - stream->codec->height, - &pixels) != ANDROID_SURFACE_RESULT_SUCCESS) { + if(!createVideoSurface(mVideoWidth, mVideoHeight)) { return INVALID_OPERATION; } - mFrame = avcodec_alloc_frame(); - if (mFrame == NULL) { - return INVALID_OPERATION; + return NO_ERROR; +} + +bool MediaPlayer::createVideoSurface(int width, int height) +{ + void* pixels; + if (Output::VideoDriver_getPixels(width, height, &pixels) + != ANDROID_SURFACE_RESULT_SUCCESS) + { + return false; } - // Assign appropriate parts of buffer to image planes in pFrameRGB - // Note that pFrameRGB is an AVFrame, but AVFrame is a superset - // of AVPicture - avpicture_fill((AVPicture *) mFrame, - (uint8_t *) pixels, - PIX_FMT_RGB565, - stream->codec->width, - stream->codec->height); - return NO_ERROR; + mFrame = avcodec_alloc_frame(); + if (mFrame == NULL) { + return false; + } + + // Assign appropriate parts of buffer to image planes in pFrameRGB + // Note that pFrameRGB is an AVFrame, but AVFrame is a superset + // of AVPicture + avpicture_fill((AVPicture *) mFrame, + (uint8_t *) pixels, + PIX_FMT_RGB565, + width, + height); + + return true; } status_t MediaPlayer::prepare() @@ -448,15 +429,35 @@ status_t MediaPlayer::pause() { Mutex::AutoLock _l(&mLock); - mCurrentState = MEDIA_PLAYER_PAUSED; + mDecodingLoop->pause(); + return NO_ERROR; } bool MediaPlayer::isPlaying() { Mutex::AutoLock _l(&mLock); - return mCurrentState == MEDIA_PLAYER_STARTED || - mCurrentState == MEDIA_PLAYER_DECODED; + return mDecodingLoop->isPlaying(); +} + +status_t MediaPlayer::setAutoscale(bool value) +{ + if (mCurrentState < MEDIA_PLAYER_PREPARED) { + return INVALID_OPERATION; + } + + mAutoscale = value; + + return NO_ERROR; +} + +status_t MediaPlayer::setResolution(int width, int height) +{ + if (mCurrentState < MEDIA_PLAYER_PREPARED) { + return INVALID_OPERATION; + } + + return NO_ERROR; } status_t MediaPlayer::getVideoWidth(int *w) diff --git a/jni/libmediaplayer/mediaplayer.h b/jni/libmediaplayer/mediaplayer.h index 1365115..6068b69 100644 --- a/jni/libmediaplayer/mediaplayer.h +++ b/jni/libmediaplayer/mediaplayer.h @@ -123,6 +123,7 @@ class DecodeLoop : public Thread DecodeLoopCallback* mCallback; bool mEnding; + bool mPaused; public: DecodeLoop(AVFormatContext* context, int audioStreamId, int videoStreamId, DecodeLoopCallback* callback); @@ -130,6 +131,9 @@ class DecodeLoop : public Thread virtual void run(); void suspend(); + void resume() { mPaused = false; }; + void pause() { mPaused = true; }; + bool isPlaying() { return !mPaused; }; }; // ---------------------------------------------------------------------------- @@ -145,6 +149,8 @@ class MediaPlayer : private DecodeLoopCallback public: MediaPlayer(); ~MediaPlayer(); + status_t setAutoscale(bool value); + status_t setResolution(int width, int height); status_t setDataSource(const char *url); status_t setVideoSurface(JNIEnv* env, jobject jsurface); status_t setListener(MediaPlayerListener *listener); @@ -171,45 +177,46 @@ class MediaPlayer : private DecodeLoopCallback status_t resume(); private: + bool createVideoSurface(int width, int height); status_t prepareAudio(); status_t prepareVideo(); bool shouldCancel(PacketQueue* queue); static void ffmpegNotify(void* ptr, int level, const char* fmt, va_list vl); - double mTime; + double mTime; - PacketQueue* mVideoQueue; Mutex mLock; - //Mutex mNotifyLock; - //Condition mSignal; MediaPlayerListener* mListener; AVFormatContext* mMovieFile; int mAudioStreamIndex; int mVideoStreamIndex; - DecodeLoop* mDecodingLoop; + DecodeLoop* mDecodingLoop; - void* mCookie; - media_player_states mCurrentState; - int mDuration; - int mCurrentPosition; - int mSeekPosition; - bool mPrepareSync; - status_t mPrepareStatus; - int mStreamType; - bool mLoop; - float mLeftVolume; - float mRightVolume; + void* mCookie; + media_player_states mCurrentState; + int mDuration; + int mCurrentPosition; + int mSeekPosition; + bool mPrepareSync; + status_t mPrepareStatus; + int mStreamType; + bool mLoop; + float mLeftVolume; + float mRightVolume; public: - struct SwsContext* mConvertCtx; - AVFrame* mFrame; - int mVideoWidth; - int mVideoHeight; + bool mAutoscale; + struct SwsContext* mConvertCtx; + AVFrame* mFrame; + int mVideoWidth; + int mVideoHeight; protected: virtual void onCompleted(); + + friend class DecodeLoop; }; #endif // FFMPEG_MEDIAPLAYER_H diff --git a/jni/libmediaplayer/output.cpp b/jni/libmediaplayer/output.cpp index 3ec32c7..945996e 100644 --- a/jni/libmediaplayer/output.cpp +++ b/jni/libmediaplayer/output.cpp @@ -68,7 +68,7 @@ int Output::VideoDriver_getPixels(int width, int height, void** pixels) return AndroidSurface_getPixels(width, height, pixels); } -int Output::VideoDriver_updateSurface() +int Output::VideoDriver_updateSurface(bool autoscale) { - return AndroidSurface_updateSurface(); -} \ No newline at end of file + return AndroidSurface_updateSurface(autoscale); +} diff --git a/jni/libmediaplayer/output.h b/jni/libmediaplayer/output.h index 705b981..210a3e7 100644 --- a/jni/libmediaplayer/output.h +++ b/jni/libmediaplayer/output.h @@ -23,7 +23,7 @@ class Output static int VideoDriver_register(JNIEnv* env, jobject jsurface); static int VideoDriver_getPixels(int width, int height, void** pixels); - static int VideoDriver_updateSurface(); + static int VideoDriver_updateSurface(bool autoscale = true); static int VideoDriver_unregister(); }; diff --git a/jni/prebuilt/libjnivideo.so b/jni/prebuilt/libjnivideo.so index 4d29d64215e2d4554b926780f27cbd2349ecbd50..48750d7b66184b003bc3f266f6fbba970e2796da 100755 GIT binary patch delta 34488 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 delta 34542 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 A4gdfE diff --git a/src/com/media/ffmpeg/FFMpegPlayer.java b/src/com/media/ffmpeg/FFMpegPlayer.java index b9492ff..798f6e5 100644 --- a/src/com/media/ffmpeg/FFMpegPlayer.java +++ b/src/com/media/ffmpeg/FFMpegPlayer.java @@ -166,6 +166,14 @@ public void pause() throws IllegalStateException { _pause(); } + public void setResolution(int width, int height) throws IllegalStateException { + _setResolution(width, height); + } + + public void setAutoscale(boolean value)throws IllegalStateException { + _setAutoscale(value); + } + /** * Set the low-level power management behavior for this MediaPlayer. This * can be used when the MediaPlayer is not playing through a SurfaceHolder @@ -271,7 +279,11 @@ private void updateSurfaceScreenOn() { private native void _stop() throws IllegalStateException; private native void _pause() throws IllegalStateException; - + + private native void _setResolution(int width, int height) throws IllegalStateException; + + private native void _setAutoscale(boolean value) throws IllegalStateException; + /** * Returns the width of the video. * diff --git a/src/com/media/ffmpeg/android/FFMpegMovieViewAndroid.java b/src/com/media/ffmpeg/android/FFMpegMovieViewAndroid.java index 831235e..9fcf147 100644 --- a/src/com/media/ffmpeg/android/FFMpegMovieViewAndroid.java +++ b/src/com/media/ffmpeg/android/FFMpegMovieViewAndroid.java @@ -53,7 +53,11 @@ private void attachMediaController() { public void setVideoPath(String filePath) throws IllegalArgumentException, IllegalStateException, IOException { mPlayer.setDataSource(filePath); } - + + public void setAutoscale(boolean value) { + mPlayer.setAutoscale(value); + } + /** * initzialize player */ @@ -68,7 +72,8 @@ private void openVideo(SurfaceHolder surfHolder) { } } - private void startVideo() { + private void startVideo(int format, int w, int h) { + //Log.d(TAG, "screen format: " + format + ", res: " + w + "x" + h); attachMediaController(); mPlayer.start(); } @@ -90,7 +95,7 @@ public boolean onTouchEvent(android.view.MotionEvent event) { SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback() { public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) { - startVideo(); + startVideo(format, w, h); } public void surfaceCreated(SurfaceHolder holder) { @@ -105,7 +110,7 @@ public void surfaceDestroyed(SurfaceHolder holder) { } }; - MediaPlayerControl mMediaPlayerControl = new MediaPlayerControl() { + private MediaPlayerControl mMediaPlayerControl = new MediaPlayerControl() { public void start() { mPlayer.resume(); diff --git a/src/cz/havlena/ffmpeg/ui/FFMpegPlayerActivity.java b/src/cz/havlena/ffmpeg/ui/FFMpegPlayerActivity.java index b740021..4b90a6f 100644 --- a/src/cz/havlena/ffmpeg/ui/FFMpegPlayerActivity.java +++ b/src/cz/havlena/ffmpeg/ui/FFMpegPlayerActivity.java @@ -10,14 +10,19 @@ import android.content.Intent; import android.os.Bundle; import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; public class FFMpegPlayerActivity extends Activity { private static final String TAG = "FFMpegPlayerActivity"; //private static final String LICENSE = "This software uses libraries from the FFmpeg project under the LGPLv2.1"; private FFMpegMovieViewAndroid mMovieView; + private boolean mAutoscale; //private WakeLock mWakeLock; + private static final int MENU_AUTOSCALE = 1; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -53,5 +58,25 @@ protected void onCreate(Bundle savedInstanceState) { finish(); } } + + mAutoscale = true; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_AUTOSCALE, 1, "Autoscale"); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch(item.getItemId()) + { + case MENU_AUTOSCALE: + mAutoscale = !mAutoscale; + mMovieView.setAutoscale(mAutoscale); + return true; + } + return false; } }