void CJNIMediaSync::queueAudio(uint8_t* audioData, int sizeInBytes, int bufferId, int64_t presentationTimeUs) { CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(sizeInBytes); void *dst_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw()); memcpy(dst_ptr, audioData, sizeInBytes); queueAudio(bytebuffer, bufferId, presentationTimeUs); }
void CDVDVideoCodecAndroidMediaCodec::ConfigureMediaCodec(void) { // setup a MediaFormat to match the video content, // used by codec during configure CJNIMediaFormat mediaformat = CJNIMediaFormat::createVideoFormat( m_mime.c_str(), m_hints.width, m_hints.height); // some android devices forget to default the demux input max size mediaformat.setInteger(CJNIMediaFormat::KEY_MAX_INPUT_SIZE, 0); // handle codec extradata if (m_hints.extrasize) { size_t size = m_hints.extrasize; void *src_ptr = m_hints.extradata; if (m_bitstream) { size = m_bitstream->GetExtraSize(); src_ptr = m_bitstream->GetExtraData(); } // Allocate a byte buffer via allocateDirect in java instead of NewDirectByteBuffer, // since the latter doesn't allocate storage of its own, and we don't know how long // the codec uses the buffer. CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(size); void *dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw()); memcpy(dts_ptr, src_ptr, size); // codec will automatically handle buffers as extradata // using entries with keys "csd-0", "csd-1", etc. mediaformat.setByteBuffer("csd-0", bytebuffer); } InitSurfaceTexture(); // configure and start the codec. // use the MediaFormat that we have setup. // use a null MediaCrypto, our content is not encrypted. // use a null Surface, we will extract the video picture data manually. int flags = 0; CJNIMediaCrypto crypto(jni::jhobject(NULL)); // our jni gets upset if we do this a different // way, do not mess with it. if (m_render_sw) { CJNISurface surface(jni::jhobject(NULL)); m_codec->configure(mediaformat, surface, crypto, flags); } else { m_codec->configure(mediaformat, *m_surface, crypto, flags); } m_codec->start(); // always, check/clear jni exceptions. if (xbmc_jnienv()->ExceptionOccurred()) xbmc_jnienv()->ExceptionClear(); }
int CAESinkAUDIOTRACK::AudioTrackWrite(char* audioData, int sizeInBytes, int64_t timestamp) { int written = 0; std::vector<char> buf; buf.reserve(sizeInBytes); memcpy(buf.data(), audioData, sizeInBytes); CJNIByteBuffer bytebuf = CJNIByteBuffer::wrap(buf); written = m_at_jni->write(bytebuf.get_raw(), sizeInBytes, CJNIAudioTrack::WRITE_BLOCKING, timestamp); return written; }
int CJNIAudioTrack::write(char* audioData, int sizeInBytes, int64_t timestamp) { int written = 0; JNIEnv* jenv = xbmc_jnienv(); char* pArray; if ((pArray = (char*)jenv->GetPrimitiveArrayCritical(m_buffer, NULL))) { memcpy(pArray, audioData, sizeInBytes); jenv->ReleasePrimitiveArrayCritical(m_buffer, pArray, 0); CJNIByteBuffer buf = CJNIByteBuffer::wrap(m_buffer); written = call_method<int>(m_object, "write", "(Ljava/nio/ByteBuffer;IIJ)I", buf.get_raw(), sizeInBytes, CJNIAudioTrack::WRITE_BLOCKING, timestamp); } return written; }
bool CDVDVideoCodecAndroidMediaCodec::ConfigureMediaCodec(void) { // setup a MediaFormat to match the video content, // used by codec during configure CJNIMediaFormat mediaformat = CJNIMediaFormat::createVideoFormat( m_mime.c_str(), m_hints.width, m_hints.height); // some android devices forget to default the demux input max size mediaformat.setInteger(CJNIMediaFormat::KEY_MAX_INPUT_SIZE, 0); // handle codec extradata if (m_hints.extrasize) { size_t size = m_hints.extrasize; void *src_ptr = m_hints.extradata; if (m_bitstream) { size = m_bitstream->GetExtraSize(); src_ptr = m_bitstream->GetExtraData(); } // Allocate a byte buffer via allocateDirect in java instead of NewDirectByteBuffer, // since the latter doesn't allocate storage of its own, and we don't know how long // the codec uses the buffer. CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(size); void *dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw()); memcpy(dts_ptr, src_ptr, size); // codec will automatically handle buffers as extradata // using entries with keys "csd-0", "csd-1", etc. mediaformat.setByteBuffer("csd-0", bytebuffer); } InitSurfaceTexture(); // configure and start the codec. // use the MediaFormat that we have setup. // use a null MediaCrypto, our content is not encrypted. // use a null Surface, we will extract the video picture data manually. int flags = 0; CJNIMediaCrypto crypto(jni::jhobject(NULL)); // our jni gets upset if we do this a different // way, do not mess with it. if (m_render_sw) { CJNISurface surface(jni::jhobject(NULL)); m_codec->configure(mediaformat, surface, crypto, flags); } else { if (m_render_surface) m_codec->configure(mediaformat, m_videosurface, crypto, flags); else m_codec->configure(mediaformat, *m_surface, crypto, flags); } // always, check/clear jni exceptions. if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::ExceptionCheck: configure"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return false; } m_state = MEDIACODEC_STATE_CONFIGURED; m_codec->start(); // always, check/clear jni exceptions. if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::ExceptionCheck: start"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return false; } m_state = MEDIACODEC_STATE_FLUSHED; // There is no guarantee we'll get an INFO_OUTPUT_FORMAT_CHANGED (up to Android 4.3) // Configure the output with defaults ConfigureOutputFormat(&mediaformat); return true; }
int CDVDVideoCodecAndroidMediaCodec::Decode(uint8_t *pData, int iSize, double dts, double pts) { if (!m_opened) return VC_ERROR; if (m_state != MEDIACODEC_STATE_RUNNING) CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::Decode current state (%d)", m_state); if (m_hints.ptsinvalid) pts = DVD_NOPTS_VALUE; // Handle input, add demuxer packet to input queue, we must accept it or // it will be discarded as VideoPlayerVideo has no concept of "try again". // we must return VC_BUFFER or VC_PICTURE, default to VC_BUFFER. bool drain = (m_codecControlFlags & DVD_CODEC_CTRL_DRAIN) ? true : false; m_dec_retcode = (drain) ? 0 : VC_BUFFER; if (!pData) { // Check if we have a saved buffer if (m_demux_pkt.pData && m_state != MEDIACODEC_STATE_ENDOFSTREAM) { pData = m_demux_pkt.pData; iSize = m_demux_pkt.iSize; pts = m_demux_pkt.pts; dts = m_demux_pkt.dts; } } else if (m_state == MEDIACODEC_STATE_ENDOFSTREAM) { // We received a packet but already reached EOS. Flush... FlushInternal(); m_codec->flush(); m_state = MEDIACODEC_STATE_FLUSHED; m_dec_retcode |= VC_BUFFER; } // must check for an output picture 1st, // otherwise, mediacodec can stall on some devices. int retgp = GetOutputPicture(); if (retgp > 0) { m_dec_retcode |= VC_PICTURE; m_noPictureLoop = 0; } else if (retgp == -1 || (drain && ++m_noPictureLoop == 10)) // EOS { m_state = MEDIACODEC_STATE_ENDOFSTREAM; m_dec_retcode |= VC_BUFFER; m_noPictureLoop = 0; } if (pData) { // try to fetch an input buffer int64_t timeout_us = 5000; int index = m_codec->dequeueInputBuffer(timeout_us); if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Decode ExceptionCheck"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); m_dec_retcode = VC_ERROR; } else if (index >= 0) { if (m_state == MEDIACODEC_STATE_FLUSHED) m_state = MEDIACODEC_STATE_RUNNING; if (!(m_state == MEDIACODEC_STATE_FLUSHED || m_state == MEDIACODEC_STATE_RUNNING)) CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Decode Dequeue: Wrong state (%d)", m_state); // we have an input buffer, fill it. if (pData && m_bitstream) { m_bitstream->Convert(pData, iSize); iSize = m_bitstream->GetConvertSize(); pData = m_bitstream->GetConvertBuffer(); } CJNIByteBuffer buffer = m_codec->getInputBuffer(index); int size = buffer.capacity(); if (iSize > size) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Decode, iSize(%d) > size(%d)", iSize, size); iSize = size; } // fetch a pointer to the ByteBuffer backing store uint8_t *dst_ptr = (uint8_t*)xbmc_jnienv()->GetDirectBufferAddress(buffer.get_raw()); if (pData && dst_ptr) { // Codec specifics switch(m_hints.codec) { case AV_CODEC_ID_VC1: { if (iSize >= 4 && pData[0] == 0x00 && pData[1] == 0x00 && pData[2] == 0x01 && (pData[3] == 0x0d || pData[3] == 0x0f)) memcpy(dst_ptr, pData, iSize); else { dst_ptr[0] = 0x00; dst_ptr[1] = 0x00; dst_ptr[2] = 0x01; dst_ptr[3] = 0x0d; memcpy(dst_ptr+4, pData, iSize); iSize += 4; } break; } default: memcpy(dst_ptr, pData, iSize); break; } } // Translate from VideoPlayer dts/pts to MediaCodec pts, // pts WILL get re-ordered by MediaCodec if needed. // Do not try to pass pts as a unioned double/int64_t, // some android devices will diddle with presentationTimeUs // and you will get NaN back and VideoPlayerVideo will barf. int64_t presentationTimeUs = 0; if (pts != DVD_NOPTS_VALUE) presentationTimeUs = pts; else if (dts != DVD_NOPTS_VALUE) presentationTimeUs = dts; /* CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: " "pts(%f), ipts(%lld), iSize(%d), GetDataSize(%d), loop_cnt(%d)", presentationTimeUs, pts_dtoi(presentationTimeUs), iSize, GetDataSize(), loop_cnt); */ int flags = 0; int offset = 0; m_codec->queueInputBuffer(index, offset, iSize, presentationTimeUs, flags); // clear any jni exceptions, jni gets upset if we do not. if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::Decode ExceptionCheck"); xbmc_jnienv()->ExceptionClear(); } // Free saved buffer it there was one if (m_demux_pkt.pData) { free(m_demux_pkt.pData); memset(&m_demux_pkt, 0, sizeof(m_demux_pkt)); } } else { // We couldn't get an input buffer. Save the packet for next iteration, if it wasn't already if (!m_demux_pkt.pData) { m_demux_pkt.dts = dts; m_demux_pkt.pts = pts; m_demux_pkt.iSize = iSize; m_demux_pkt.pData = (uint8_t*)malloc(iSize); memcpy(m_demux_pkt.pData, pData, iSize); } m_dec_retcode &= ~VC_BUFFER; } } return m_dec_retcode; }
int CDVDVideoCodecAndroidMediaCodec::GetOutputPicture(void) { int rtn = 0; int64_t timeout_us = 10000; CJNIMediaCodecBufferInfo bufferInfo; int index = m_codec->dequeueOutputBuffer(bufferInfo, timeout_us); if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture ExceptionCheck; dequeueOutputBuffer"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return -2; } if (index >= 0) { int64_t pts= bufferInfo.presentationTimeUs(); m_videobuffer.dts = DVD_NOPTS_VALUE; m_videobuffer.pts = DVD_NOPTS_VALUE; if (pts != AV_NOPTS_VALUE) m_videobuffer.pts = pts; int flags = bufferInfo.flags(); if (flags & CJNIMediaCodec::BUFFER_FLAG_SYNC_FRAME) CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_SYNC_FRAME"); if (flags & CJNIMediaCodec::BUFFER_FLAG_CODEC_CONFIG) CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_CODEC_CONFIG"); if (flags & CJNIMediaCodec::BUFFER_FLAG_END_OF_STREAM) { CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec:: BUFFER_FLAG_END_OF_STREAM"); m_codec->releaseOutputBuffer(index, false); if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture ExceptionCheck: releaseOutputBuffer"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return -2; } return -1; } if (m_drop) { m_codec->releaseOutputBuffer(index, false); if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture ExceptionCheck: releaseOutputBuffer"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return -2; } m_videobuffer.mediacodec = nullptr; return 1; } if (!m_render_sw) { size_t i = 0; for (; i < m_inflight.size(); ++i) { if (m_inflight[i]->GetIndex() == index) break; } if (i == m_inflight.size()) m_inflight.push_back( new CDVDMediaCodecInfo(index, m_textureId, m_codec, m_surfaceTexture, m_frameAvailable) ); m_videobuffer.mediacodec = m_inflight[i]->Retain(); m_videobuffer.mediacodec->Validate(true); } else { int size = bufferInfo.size(); int offset = bufferInfo.offset(); CJNIByteBuffer buffer = m_codec->getOutputBuffer(index);; if (!buffer.isDirect()) CLog::Log(LOGWARNING, "CDVDVideoCodecAndroidMediaCodec:: buffer.isDirect == false"); if (!buffer.isDirect()) CLog::Log(LOGWARNING, "CDVDVideoCodecAndroidMediaCodec:: m_output[index].isDirect == false"); if (size && buffer.capacity()) { uint8_t *src_ptr = (uint8_t*)xbmc_jnienv()->GetDirectBufferAddress(buffer.get_raw()); src_ptr += offset; int loop_end = 0; if (m_videobuffer.format == RENDER_FMT_NV12) loop_end = 2; else if (m_videobuffer.format == RENDER_FMT_YUV420P) loop_end = 3; for (int i = 0; i < loop_end; i++) { uint8_t *src = src_ptr + m_src_offset[i]; int src_stride = m_src_stride[i]; uint8_t *dst = m_videobuffer.data[i]; int dst_stride = m_videobuffer.iLineSize[i]; int height = m_videobuffer.iHeight; if (i > 0) height = (m_videobuffer.iHeight + 1) / 2; if (src_stride == dst_stride) memcpy(dst, src, dst_stride * height); else for (int j = 0; j < height; j++, src += src_stride, dst += dst_stride) memcpy(dst, src, dst_stride); } } m_codec->releaseOutputBuffer(index, false); } /* CLog::Log(LOGDEBUG, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture " "index(%d), pts(%f)", index, m_videobuffer.pts); */ // always, check/clear jni exceptions. if (xbmc_jnienv()->ExceptionCheck()) xbmc_jnienv()->ExceptionClear(); rtn = 1; } else if (index == CJNIMediaCodec::INFO_OUTPUT_FORMAT_CHANGED) { CJNIMediaFormat mediaformat = m_codec->getOutputFormat(); if (xbmc_jnienv()->ExceptionCheck()) { xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); } ConfigureOutputFormat(&mediaformat); } else if (index == CJNIMediaCodec::INFO_TRY_AGAIN_LATER || index == CJNIMediaCodec::INFO_OUTPUT_BUFFERS_CHANGED) { // ignore rtn = 0; } else { // we should never get here CLog::Log(LOGERROR, "CDVDVideoCodecAndroidMediaCodec::GetOutputPicture unknown index(%d)", index); rtn = -2; } return rtn; }
int CJNIByteBuffer::compareTo(const CJNIByteBuffer &otherBuffer) { return call_method<int>(m_object, "compareTo","(Ljava/nio/ByteBuffer;)I", otherBuffer.get_raw()); }
CJNIByteBuffer CJNIByteBuffer::put(const CJNIByteBuffer &src) { return CJNIByteBuffer(call_method<jhobject>(m_object, "put","(Ljava/nio/ByteBuffer;)Ljava/nio/ByteBuffer;", src.get_raw())); }
bool CDVDAudioCodecAndroidMediaCodec::ConfigureMediaCodec(void) { // setup a MediaFormat to match the audio content, // used by codec during configure CJNIMediaFormat mediaformat = CJNIMediaFormat::createAudioFormat( m_mime.c_str(), m_hints.samplerate, m_hints.channels); // handle codec extradata if (m_hints.extrasize) { size_t size = m_hints.extrasize; void *src_ptr = m_hints.extradata; // Allocate a byte buffer via allocateDirect in java instead of NewDirectByteBuffer, // since the latter doesn't allocate storage of its own, and we don't know how long // the codec uses the buffer. CJNIByteBuffer bytebuffer = CJNIByteBuffer::allocateDirect(size); void *dts_ptr = xbmc_jnienv()->GetDirectBufferAddress(bytebuffer.get_raw()); memcpy(dts_ptr, src_ptr, size); // codec will automatically handle buffers as extradata // using entries with keys "csd-0", "csd-1", etc. mediaformat.setByteBuffer("csd-0", bytebuffer); } else if (m_hints.codec == AV_CODEC_ID_AAC || m_hints.codec == AV_CODEC_ID_AAC_LATM) { mediaformat.setInteger(CJNIMediaFormat::KEY_IS_ADTS, 1); } // configure and start the codec. // use the MediaFormat that we have setup. // use a null MediaCrypto, our content is not encrypted. // use a null Surface int flags = 0; CJNIMediaCrypto crypto(jni::jhobject(NULL)); CJNISurface surface(jni::jhobject(NULL)); m_codec->configure(mediaformat, surface, crypto, flags); // always, check/clear jni exceptions. if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::ExceptionCheck: configure"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return false; } m_codec->start(); // always, check/clear jni exceptions. if (xbmc_jnienv()->ExceptionCheck()) { CLog::Log(LOGERROR, "CDVDAudioCodecAndroidMediaCodec::ExceptionCheck: start"); xbmc_jnienv()->ExceptionDescribe(); xbmc_jnienv()->ExceptionClear(); return false; } // There is no guarantee we'll get an INFO_OUTPUT_FORMAT_CHANGED (up to Android 4.3) // Configure the output with defaults ConfigureOutputFormat(&mediaformat); return true; }
void CJNIMediaFormat::setByteBuffer(const std::string &name, CJNIByteBuffer &bytes) { call_method<void>(m_object, "setByteBuffer", "(Ljava/lang/String;Ljava/nio/ByteBuffer;)V", jcast<jhstring>(name), bytes.get_raw()); }
void CJNIMediaSync::queueAudio(const CJNIByteBuffer& audioData, int bufferId, int64_t presentationTimeUs) { call_method<void>(m_object, "queueAudio", "(Ljava/nio/ByteBuffer;IJ)V", audioData.get_raw(), bufferId, presentationTimeUs); }