diff --git a/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java b/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java index b4a074985..8056582ec 100644 --- a/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java +++ b/app/src/main/java/uk/openvk/android/legacy/utils/media/OvkMediaPlayer.java @@ -80,6 +80,7 @@ public class OvkMediaPlayer extends MediaPlayer { private OnCompletionListener onCompletionListener; private Handler handler; private AudioTrack audio_track; + private byte[] videoBuffer; // C++ player native functions private native void naInit(); @@ -319,6 +320,7 @@ private void renderAudio(final byte[] buffer, final int length) { audio_track.play(); prepared_audio_buffer = true; } + try { audio_track.write(buffer, 0, length); } catch (Exception ignored) { @@ -330,67 +332,69 @@ private void completePlayback() { } private void renderVideo(final byte[] buffer, final int length) { - new Thread(new Runnable() { - @Override - public void run() { - Canvas c; - OvkVideoTrack track = null; - for (int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) { - if (tracks.get(tracks_index) instanceof OvkVideoTrack) { - track = (OvkVideoTrack) tracks.get(tracks_index); - } - } - if (track != null) { - int frame_width = track.frame_size[0]; - int frame_height = track.frame_size[1]; - if (frame_width > 0 && frame_height > 0) { - minVideoBufferSize = frame_width * frame_height * 4; - try { - // RGB_565 == 65K colours (16 bit) - // RGB_8888 == 16.7M colours (24 bit w/ alpha ch.) - int bpp = Build.VERSION.SDK_INT > 9 ? 16 : 24; - Bitmap.Config bmp_config = - bpp == 24 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888; - if(buffer != null && holder != null) { - holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); - if((c = holder.lockCanvas()) == null) { - Log.d(MPLAY_TAG, "Lock canvas failed"); - return; - } - ByteBuffer bbuf = - ByteBuffer.allocateDirect(minVideoBufferSize); - bbuf.rewind(); - for(int i = 0; i < buffer.length; i++) { - bbuf.put(i, buffer[i]); - } - bbuf.rewind(); - Bitmap bmp = Bitmap.createBitmap(frame_width, frame_height, bmp_config); - bmp.copyPixelsFromBuffer(bbuf); - float aspect_ratio = (float) frame_width / (float) frame_height; - int scaled_width = (int)(aspect_ratio * (c.getHeight())); - c.drawBitmap(bmp, - null, - new RectF( - ((c.getWidth() - scaled_width) / 2), 0, - ((c.getWidth() - scaled_width) / 2) + scaled_width, - c.getHeight()), - null); - holder.unlockCanvasAndPost(c); - bmp.recycle(); - bbuf.clear(); - } else { - Log.d(MPLAY_TAG, "Video frame buffer is null"); - } - } catch (Exception ex) { - ex.printStackTrace(); - } catch (OutOfMemoryError oom) { - oom.printStackTrace(); - stop(); + Canvas c; + videoBuffer = buffer; + OvkVideoTrack track = null; + for (int tracks_index = 0; tracks_index < tracks.size(); tracks_index++) { + if (tracks.get(tracks_index) instanceof OvkVideoTrack) { + track = (OvkVideoTrack) tracks.get(tracks_index); + } + } + if (track != null) { + int frame_width = track.frame_size[0]; + int frame_height = track.frame_size[1]; + if (frame_width > 0 && frame_height > 0) { + minVideoBufferSize = frame_width * frame_height * 4; + try { + // RGB_565 == 65K colours (16 bit) + // RGB_8888 == 16.7M colours (24 bit w/ alpha ch.) + int bpp = Build.VERSION.SDK_INT > 9 ? 16 : 24; + Bitmap.Config bmp_config = + bpp == 24 ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888; + if(videoBuffer != null && holder != null) { + holder.setType(SurfaceHolder.SURFACE_TYPE_NORMAL); + if((c = holder.lockCanvas()) == null) { + Log.d(MPLAY_TAG, "Lock canvas failed"); + return; } + ByteBuffer bbuf = + ByteBuffer.allocateDirect(minVideoBufferSize); + bbuf.rewind(); + for(int i = 0; i < videoBuffer.length; i++) { + bbuf.put(i, videoBuffer[i]); + } + bbuf.rewind(); + Bitmap bmp = Bitmap.createBitmap(frame_width, frame_height, bmp_config); + bmp.copyPixelsFromBuffer(bbuf); + float aspect_ratio = (float) frame_width / (float) frame_height; + int scaled_width = (int)(aspect_ratio * (c.getHeight())); + videoBuffer = null; + c.drawBitmap(bmp, + null, + new RectF( + ((c.getWidth() - scaled_width) / 2), 0, + ((c.getWidth() - scaled_width) / 2) + scaled_width, + c.getHeight()), + null); + holder.unlockCanvasAndPost(c); + bmp.recycle(); + bbuf.clear(); + } else { + Log.d(MPLAY_TAG, "Video frame buffer is null"); } + } catch (Exception ex) { + ex.printStackTrace(); + } catch (OutOfMemoryError oom) { + oom.printStackTrace(); + stop(); + } + try { + Thread.sleep((long) (1000 / track.frame_rate)); + } catch (InterruptedException e) { + e.printStackTrace(); } } - }).start(); + } } @Override diff --git a/ndk-modules/ovkmplayer/decoders/audiodec.cpp b/ndk-modules/ovkmplayer/decoders/audiodec.cpp index 48df9d00e..ac3a87328 100644 --- a/ndk-modules/ovkmplayer/decoders/audiodec.cpp +++ b/ndk-modules/ovkmplayer/decoders/audiodec.cpp @@ -67,9 +67,7 @@ void *AudioDecoder::decodeInThread() { } bool AudioDecoder::start() { - pthread_t decoderThread; - pthread_create(&decoderThread, NULL, s_decodeInThread, (void*)this); - pthread_join(decoderThread, NULL); + decodeInThread(); return true; } diff --git a/ndk-modules/ovkmplayer/decoders/audiodec.h b/ndk-modules/ovkmplayer/decoders/audiodec.h index a91edd600..88dca2a12 100644 --- a/ndk-modules/ovkmplayer/decoders/audiodec.h +++ b/ndk-modules/ovkmplayer/decoders/audiodec.h @@ -8,6 +8,7 @@ #include <../utils/pktqueue.h> #include <../interfaces/ffwrap.h> #include +#include #define LOG_TAG "FFwrap" #define LOG_LEVEL 10 diff --git a/ndk-modules/ovkmplayer/decoders/videodec.cpp b/ndk-modules/ovkmplayer/decoders/videodec.cpp index 59c70c38c..fa34e8029 100644 --- a/ndk-modules/ovkmplayer/decoders/videodec.cpp +++ b/ndk-modules/ovkmplayer/decoders/videodec.cpp @@ -17,8 +17,7 @@ VideoDecoder::VideoDecoder(AVFormatContext *pFormatCtx, } bool VideoDecoder::prepare() { - gFrame = avcodec_alloc_frame(); - return gFrame != NULL; + return true; } static void *s_decodeInThread(void *arg) { @@ -26,23 +25,87 @@ static void *s_decodeInThread(void *arg) { } void *VideoDecoder::decodeInThread() { - int status, dataSize, len; - AVPacket avPkt; + AVPacket avPkt; + int vWidth = gCodecCtx->width, + vHeight = gCodecCtx->height, + status, len, + dataSize = avpicture_get_size(AV_PIX_FMT_RGB32, vWidth, vHeight), + packetSize, tVideoFrames; + struct SwsContext *img_convert_ctx = NULL; + + gBuffer = (short*) av_mallocz((size_t)dataSize); while(av_read_frame(gFormatCtx, &avPkt)>=0) { + gFrame = avcodec_alloc_frame(); // It is from the video stream? if(avPkt.stream_index == gStreamIndex) { + packetSize = avPkt.size; + struct SwsContext *img_convert_ctx = NULL; + avpicture_fill((AVPicture*) gFrame, + (const uint8_t*) gBuffer, + gCodecCtx->pix_fmt, + gCodecCtx->width, + gCodecCtx->height + ); + + avcodec_decode_video2(gCodecCtx, gFrame, &status, &avPkt); + if(!status || gFrame == NULL || packetSize == 0) { + tVideoFrames++; + continue; + } + AVPixelFormat pxf; - // TODO: Implement video decoding stages - //gInterface->onStreamDecoding((uint8_t*)gBuffer, dataSize / 2, gStreamIndex); + pxf = AV_PIX_FMT_BGR32; + + convertYuv2Rgb(pxf, gFrame, dataSize); + tVideoFrames++; + gInterface->onStreamDecoding((uint8_t*)gBuffer, dataSize, gStreamIndex); } + av_free(gFrame); // Free the packet that was allocated by av_read_frame av_free_packet(&avPkt); } + av_free(gBuffer); + stop(); } +bool VideoDecoder::start() { + decodeInThread(); + return true; +} + +short* VideoDecoder::convertYuv2Rgb(AVPixelFormat pxf, AVFrame* frame, int length) { + AVFrame *frameRGB = av_frame_alloc(); + AVPixelFormat output_pxf = pxf; + + avpicture_fill((AVPicture *)frameRGB, (uint8_t*)gBuffer, output_pxf, + gCodecCtx->width, gCodecCtx->height); + const int width = gCodecCtx->width, height = gCodecCtx->height; + SwsContext* img_convert_ctx = sws_getContext(width, height, + gCodecCtx->pix_fmt, + width, height, output_pxf, SWS_BICUBIC, + NULL, NULL, NULL); + + + if(img_convert_ctx == NULL) { + LOGE(10, "Cannot initialize the conversion context!"); + sws_freeContext(img_convert_ctx); + return NULL; + } + + int ret = sws_scale(img_convert_ctx, (const uint8_t* const*)frame->data, frame->linesize, 0, + gCodecCtx->height, frameRGB->data, frameRGB->linesize); + if(frameRGB->data[0] == NULL) { + LOGE(10, "SWS_Scale failed"); + } + av_free(frameRGB); + av_frame_unref(frameRGB); + sws_freeContext(img_convert_ctx); + return gBuffer; +} + bool VideoDecoder::stop() { av_free(gFrame); avcodec_close(gCodecCtx); diff --git a/ndk-modules/ovkmplayer/decoders/videodec.h b/ndk-modules/ovkmplayer/decoders/videodec.h index 2ff7a241b..b3d029764 100644 --- a/ndk-modules/ovkmplayer/decoders/videodec.h +++ b/ndk-modules/ovkmplayer/decoders/videodec.h @@ -7,6 +7,8 @@ #include <../utils/pktqueue.h> +#include + #define LOG_TAG "FFwrap" #define LOG_LEVEL 10 #define LOGD(level, ...) if (level <= LOG_LEVEL) {__android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__);} @@ -70,9 +72,10 @@ class VideoDecoder { AVStream *gStream; IFFmpegWrapper *gInterface; AVFrame *gFrame; - bool start(); - bool stop(); - void* decodeInThread(); + bool start(); + bool stop(); + void* decodeInThread(); + short* convertYuv2Rgb(AVPixelFormat pxf, AVFrame* frame, int length); private: PacketQueue* gPktQueue; }; diff --git a/ndk-modules/ovkmplayer/interfaces/ffwrap.h b/ndk-modules/ovkmplayer/interfaces/ffwrap.h index a14880e99..06e442455 100644 --- a/ndk-modules/ovkmplayer/interfaces/ffwrap.h +++ b/ndk-modules/ovkmplayer/interfaces/ffwrap.h @@ -3,6 +3,8 @@ #include +#include + class IFFmpegWrapper { public: IFFmpegWrapper() {}; @@ -15,8 +17,11 @@ class IFFmpegWrapper { int bufferLen, int streamIndex) = 0; virtual void onChangePlaybackState(int playbackState) = 0; + virtual void onChangeWrapperState(int wrapperState) = 0; + JNIEnv *env; + jobject instance; private: bool gDebugMode; }; -#endif \ No newline at end of file +#endif diff --git a/ndk-modules/ovkmplayer/ovkmplay.cpp b/ndk-modules/ovkmplayer/ovkmplay.cpp index 369840a79..613d3b63e 100644 --- a/ndk-modules/ovkmplayer/ovkmplay.cpp +++ b/ndk-modules/ovkmplayer/ovkmplay.cpp @@ -71,7 +71,8 @@ int gErrorCode; bool gDebugMode = true; -int gFramesCount; +int gFramesCount, + gAttachResult = -54; FFmpegWrapper *gWrapper; @@ -90,19 +91,38 @@ class IPlayerWrapper : public IFFmpegWrapper { int bufferLen, int streamIndex); void onChangePlaybackState(int playbackState) {}; + void onChangeWrapperState(int wrapperState); + JNIEnv *env; + jobject instance; }; +IPlayerWrapper* gInterface; + +int attachEnv(JNIEnv **pEnv) { + int getEnvStat = gVM->GetEnv((void **) pEnv, JNI_VERSION_1_6); + if (getEnvStat == JNI_EDETACHED) { + if (gVM->AttachCurrentThread(pEnv, NULL) != 0) { + LOGE(10, "Failed to attach thread with JNIEnv*"); + return 2; //Failed to attach + } + return 1; //Attached. Need detach + } else if (JNI_OK == getEnvStat) { + return 0;//Already attached + } else { + return 3; + } +} + JNIEXPORT void JNICALL naInit(JNIEnv *env, jobject instance) { - IFFmpegWrapper* interface = new IPlayerWrapper(); - gWrapper = new FFmpegWrapper(gDebugMode, interface); - env->GetJavaVM(&gVM); - gInstance = env->NewGlobalRef(instance); + gInterface = new IPlayerWrapper(); + gInterface->instance = env->NewGlobalRef(instance); + gWrapper = new FFmpegWrapper(gDebugMode, gInterface); +} + +JNIEXPORT void JNICALL naPlay(JNIEnv *env, jobject instance, int streamType) { gVMArgs.version = JNI_VERSION_1_6; gVMArgs.name = NULL; gVMArgs.group = NULL; -} - -JNIEXPORT void JNICALL naPlay(JNIEnv *env, jobject instance) { gWrapper->setPlaybackState(FFMPEG_PLAYBACK_PLAYING); gWrapper->startDecoding(); } @@ -146,38 +166,50 @@ void IPlayerWrapper::onError(int cmdId, int errorCode) { void IPlayerWrapper::onResult(int cmdId, int resultCode) { JNIEnv* env; - gVM->AttachCurrentThread(&env, &gVMArgs); - - if(cmdId == FFMPEG_COMMAND_FIND_STREAMS) { - gWrapper->openCodecs(); - } else if(cmdId == FFMPEG_COMMAND_OPEN_CODECS) { - jclass jmPlay = env->GetObjectClass(gInstance); - jmethodID onResultMid = env->GetMethodID(jmPlay, "onResult", "(II)V"); - env->CallVoidMethod(gInstance, onResultMid, (jint)cmdId, (jint)resultCode); + int attachResult = attachEnv(&env); + if(attachResult < 2) { + jclass jmPlay = env->GetObjectClass(instance); + if(cmdId == FFMPEG_COMMAND_FIND_STREAMS) { + gWrapper->openCodecs(); + } else if(cmdId == FFMPEG_COMMAND_OPEN_CODECS) { + jmethodID onResultMid = env->GetMethodID(jmPlay, "onResult", "(II)V"); + env->CallVoidMethod(instance, onResultMid, (jint)cmdId, (jint)resultCode); + } + if(attachResult == 1) { + gVM->DetachCurrentThread(); + } } - //gVM->DetachCurrentThread(); } void IPlayerWrapper::onStreamDecoding(uint8_t* buffer, int bufferLen, int streamIndex) { JNIEnv* env; - gVM->AttachCurrentThread(&env, &gVMArgs); - jBuffer = env->NewByteArray(bufferLen); - jclass jmPlay = env->GetObjectClass(gInstance); - if(streamIndex == gWrapper->gAudioStreamIndex) { - jmethodID renderAudioMid = env->GetMethodID(jmPlay, "renderAudio", "([BI)V"); + int attachResult = attachEnv(&env); + if(attachResult < 2) { + jclass jmPlay = env->GetObjectClass(instance); + jBuffer = env->NewByteArray((jsize) bufferLen); env->SetByteArrayRegion(jBuffer, 0, (jsize) bufferLen, (jbyte *) buffer); - env->CallVoidMethod(gInstance, renderAudioMid, jBuffer, bufferLen); - } else if(streamIndex == gWrapper->gVideoStreamIndex) { - + if(streamIndex == gWrapper->gAudioStreamIndex) { + jmethodID renderAudioMid = env->GetMethodID(jmPlay, "renderAudio", "([BI)V"); + env->CallVoidMethod(instance, renderAudioMid, jBuffer, bufferLen); + } else if(streamIndex == gWrapper->gVideoStreamIndex) { + jmethodID renderVideoMid = env->GetMethodID(jmPlay, "renderVideo", "([BI)V"); + env->CallVoidMethod(instance, renderVideoMid, jBuffer, bufferLen); + } + env->ReleaseByteArrayElements(jBuffer, (jbyte *)env->GetByteArrayElements(jBuffer, NULL), JNI_ABORT); + env->DeleteLocalRef(jBuffer); + env->DeleteLocalRef(jmPlay); + if(attachResult == 1) { + gVM->DetachCurrentThread(); + } } - //env->ReleaseByteArrayElements(jBuffer, (jbyte *)buffer, 0); - gVM->DetachCurrentThread(); +} + +void IPlayerWrapper::onChangeWrapperState(int wrapperState) { } JNIEXPORT jobject JNICALL naGenerateTrackInfo( JNIEnv* env, jobject instance, jint type ) { - jclass track_class; try { AVStream *pStream; diff --git a/ndk-modules/ovkmplayer/utils/ffwrap.cpp b/ndk-modules/ovkmplayer/utils/ffwrap.cpp index f5147e850..faba164d0 100644 --- a/ndk-modules/ovkmplayer/utils/ffwrap.cpp +++ b/ndk-modules/ovkmplayer/utils/ffwrap.cpp @@ -4,6 +4,9 @@ #include "ffwrap.h" +#include +#include + FFmpegWrapper::FFmpegWrapper(bool pDebugMode, IFFmpegWrapper *pInterface) { gInterface = pInterface; setDebugMode(pDebugMode); @@ -136,16 +139,38 @@ AVStream* FFmpegWrapper::getStream(int index) { return gFormatCtx->streams[index]; } -void FFmpegWrapper::startDecoding() { - AudioDecoder *audioDec = new AudioDecoder( - gFormatCtx, - gAudioCodecCtx, - getStream(gAudioStreamIndex), - gAudioStreamIndex, - gInterface - ); +static void *audioDecoderThread(void *arg) { + AudioDecoder *audioDec = (AudioDecoder*) arg; audioDec->prepare(); audioDec->start(); } +static void *videoDecoderThread(void *arg) { + VideoDecoder *videoDec = (VideoDecoder*) arg; + videoDec->prepare(); + videoDec->start(); +} + +void FFmpegWrapper::startDecoding() { + AudioDecoder *audioDec = new AudioDecoder( + gFormatCtx, + gAudioCodecCtx, + getStream(gAudioStreamIndex), + gAudioStreamIndex, + gInterface + ); + VideoDecoder *videoDec = new VideoDecoder( + gFormatCtx, + gVideoCodecCtx, + getStream(gVideoStreamIndex), + gVideoStreamIndex, + gInterface + ); + pthread_t audioDecThread; + pthread_create(&audioDecThread, NULL, &audioDecoderThread, (void*)audioDec); + pthread_t videoDecThread; + pthread_create(&videoDecThread, NULL, &videoDecoderThread, (void*)videoDec); + +} + diff --git a/ndk-modules/ovkmplayer/utils/ffwrap.h b/ndk-modules/ovkmplayer/utils/ffwrap.h index 1129af2d8..dbbfa2df2 100644 --- a/ndk-modules/ovkmplayer/utils/ffwrap.h +++ b/ndk-modules/ovkmplayer/utils/ffwrap.h @@ -27,6 +27,7 @@ extern "C"{ #include <../interfaces/ffwrap.h> #include <../decoders/audiodec.h> +#include <../decoders/videodec.h> // FFmpeg implementation headers (using LGPLv3.0 model) extern "C" {