text: /*
   * Copyright (C) 2011 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.
   */
  
  //#define LOG_NDEBUG 0
  #define LOG_TAG "SoftVorbis"
  #include <utils/Log.h>
  
  #include "SoftVorbis.h"
  
  #include <media/stagefright/foundation/ADebug.h>
  #include <media/stagefright/MediaDefs.h>
  
  extern "C" {
      #include <Tremolo/codec_internal.h>
  
      int _vorbis_unpack_books(vorbis_info *vi,oggpack_buffer *opb);
      int _vorbis_unpack_info(vorbis_info *vi,oggpack_buffer *opb);
      int _vorbis_unpack_comment(vorbis_comment *vc,oggpack_buffer *opb);
  }
  
  namespace android {
  
  template<class T>
  static void InitOMXParams(T *params) {
      params->nSize = sizeof(T);
      params->nVersion.s.nVersionMajor = 1;
      params->nVersion.s.nVersionMinor = 0;
      params->nVersion.s.nRevision = 0;
      params->nVersion.s.nStep = 0;
  }
  
  SoftVorbis::SoftVorbis(
          const char *name,
          const OMX_CALLBACKTYPE *callbacks,
          OMX_PTR appData,
          OMX_COMPONENTTYPE **component)
      : SimpleSoftOMXComponent(name, callbacks, appData, component),
        mInputBufferCount(0),
        mState(NULL),
        mVi(NULL),
        mAnchorTimeUs(0),
        mNumFramesOutput(0),
        mNumFramesLeftOnPage(-1),
        mSawInputEos(false),
        mSignalledOutputEos(false),
        mSignalledError(false),
        mOutputPortSettingsChange(NONE) {
      initPorts();
      CHECK_EQ(initDecoder(), (status_t)OK);
  }
  
  SoftVorbis::~SoftVorbis() {
      if (mState != NULL) {
          vorbis_dsp_clear(mState);
          delete mState;
          mState = NULL;
      }
  
      if (mVi != NULL) {
          vorbis_info_clear(mVi);
          delete mVi;
          mVi = NULL;
      }
  }
  
  void SoftVorbis::initPorts() {
      OMX_PARAM_PORTDEFINITIONTYPE def;
      InitOMXParams(&def);
  
      def.nPortIndex = 0;
      def.eDir = OMX_DirInput;
      def.nBufferCountMin = kNumBuffers;
      def.nBufferCountActual = def.nBufferCountMin;
      def.nBufferSize = kMaxNumSamplesPerBuffer * sizeof(int16_t);
      def.bEnabled = OMX_TRUE;
      def.bPopulated = OMX_FALSE;
      def.eDomain = OMX_PortDomainAudio;
      def.bBuffersContiguous = OMX_FALSE;
      def.nBufferAlignment = 1;
  
      def.format.audio.cMIMEType =
          const_cast<char *>(MEDIA_MIMETYPE_AUDIO_VORBIS);
  
      def.format.audio.pNativeRender = NULL;
      def.format.audio.bFlagErrorConcealment = OMX_FALSE;
      def.format.audio.eEncoding = OMX_AUDIO_CodingVORBIS;
  
      addPort(def);
  
      def.nPortIndex = 1;
      def.eDir = OMX_DirOutput;
      def.nBufferCountMin = kNumBuffers;
      def.nBufferCountActual = def.nBufferCountMin;
      def.nBufferSize = kMaxNumSamplesPerBuffer * sizeof(int16_t);
      def.bEnabled = OMX_TRUE;
      def.bPopulated = OMX_FALSE;
      def.eDomain = OMX_PortDomainAudio;
      def.bBuffersContiguous = OMX_FALSE;
      def.nBufferAlignment = 2;
  
      def.format.audio.cMIMEType = const_cast<char *>("audio/raw");
      def.format.audio.pNativeRender = NULL;
      def.format.audio.bFlagErrorConcealment = OMX_FALSE;
      def.format.audio.eEncoding = OMX_AUDIO_CodingPCM;
  
      addPort(def);
  }
  
  status_t SoftVorbis::initDecoder() {
      return OK;
  }
  
  OMX_ERRORTYPE SoftVorbis::internalGetParameter(
          OMX_INDEXTYPE index, OMX_PTR params) {
      switch (index) {
          case OMX_IndexParamAudioVorbis:
          {
              OMX_AUDIO_PARAM_VORBISTYPE *vorbisParams =
                  (OMX_AUDIO_PARAM_VORBISTYPE *)params;
  
              if (!isValidOMXParam(vorbisParams)) {
                  return OMX_ErrorBadParameter;
              }
  
              if (vorbisParams->nPortIndex != 0) {
                  return OMX_ErrorUndefined;
              }
  
              vorbisParams->nBitRate = 0;
              vorbisParams->nMinBitRate = 0;
              vorbisParams->nMaxBitRate = 0;
              vorbisParams->nAudioBandWidth = 0;
              vorbisParams->nQuality = 3;
              vorbisParams->bManaged = OMX_FALSE;
              vorbisParams->bDownmix = OMX_FALSE;
  
              if (!isConfigured()) {
                  vorbisParams->nChannels = 1;
                  vorbisParams->nSampleRate = 44100;
              } else {
                  vorbisParams->nChannels = mVi->channels;
                  vorbisParams->nSampleRate = mVi->rate;
                  vorbisParams->nBitRate = mVi->bitrate_nominal;
                  vorbisParams->nMinBitRate = mVi->bitrate_lower;
                  vorbisParams->nMaxBitRate = mVi->bitrate_upper;
              }
  
              return OMX_ErrorNone;
          }
  
          case OMX_IndexParamAudioPcm:
          {
              OMX_AUDIO_PARAM_PCMMODETYPE *pcmParams =
                  (OMX_AUDIO_PARAM_PCMMODETYPE *)params;
  
              if (!isValidOMXParam(pcmParams)) {
                  return OMX_ErrorBadParameter;
              }
  
              if (pcmParams->nPortIndex != 1) {
                  return OMX_ErrorUndefined;
              }
  
              pcmParams->eNumData = OMX_NumericalDataSigned;
              pcmParams->eEndian = OMX_EndianBig;
              pcmParams->bInterleaved = OMX_TRUE;
              pcmParams->nBitPerSample = 16;
              pcmParams->ePCMMode = OMX_AUDIO_PCMModeLinear;
              pcmParams->eChannelMapping[0] = OMX_AUDIO_ChannelLF;
              pcmParams->eChannelMapping[1] = OMX_AUDIO_ChannelRF;
  
              if (!isConfigured()) {
                  pcmParams->nChannels = 1;
                  pcmParams->nSamplingRate = 44100;
              } else {
                  pcmParams->nChannels = mVi->channels;
                  pcmParams->nSamplingRate = mVi->rate;
              }
  
              return OMX_ErrorNone;
          }
  
          default:
              return SimpleSoftOMXComponent::internalGetParameter(index, params);
      }
  }
  
  OMX_ERRORTYPE SoftVorbis::internalSetParameter(
          OMX_INDEXTYPE index, const OMX_PTR params) {
      switch (index) {
          case OMX_IndexParamStandardComponentRole:
          {
              const OMX_PARAM_COMPONENTROLETYPE *roleParams =
                  (const OMX_PARAM_COMPONENTROLETYPE *)params;
  
              if (!isValidOMXParam(roleParams)) {
                  return OMX_ErrorBadParameter;
              }
  
              if (strncmp((const char *)roleParams->cRole,
                          "audio_decoder.vorbis",
                          OMX_MAX_STRINGNAME_SIZE - 1)) {
                  return OMX_ErrorUndefined;
              }
  
              return OMX_ErrorNone;
          }
  
          case OMX_IndexParamAudioVorbis:
          {
              const OMX_AUDIO_PARAM_VORBISTYPE *vorbisParams =
                  (const OMX_AUDIO_PARAM_VORBISTYPE *)params;
  
              if (!isValidOMXParam(vorbisParams)) {
                  return OMX_ErrorBadParameter;
              }
  
              if (vorbisParams->nPortIndex != 0) {
                  return OMX_ErrorUndefined;
              }
  
              return OMX_ErrorNone;
          }
  
          default:
              return SimpleSoftOMXComponent::internalSetParameter(index, params);
      }
  }
  
  bool SoftVorbis::isConfigured() const {
      return mInputBufferCount >= 2;
  }
  
  static void makeBitReader(
          const void *data, size_t size,
          ogg_buffer *buf, ogg_reference *ref, oggpack_buffer *bits) {
      buf->data = (uint8_t *)data;
      buf->size = size;
      buf->refcount = 1;
      buf->ptr.owner = NULL;
  
      ref->buffer = buf;
      ref->begin = 0;
      ref->length = size;
      ref->next = NULL;
  
      oggpack_readinit(bits, ref);
  }
  
  void SoftVorbis::onQueueFilled(OMX_U32 portIndex) {
      List<BufferInfo *> &inQueue = getPortQueue(0);
      List<BufferInfo *> &outQueue = getPortQueue(1);
  
      if (mSignalledError || mOutputPortSettingsChange != NONE) {
          return;
      }
  
      if (portIndex == 0 && mInputBufferCount < 2) {
          BufferInfo *info = *inQueue.begin();
          OMX_BUFFERHEADERTYPE *header = info->mHeader;
  
          const uint8_t *data = header->pBuffer + header->nOffset;
          size_t size = header->nFilledLen;
          if (size < 7) {
              ALOGE("Too small input buffer: %zu bytes", size);
              android_errorWriteLog(0x534e4554, "27833616");
              notify(OMX_EventError, OMX_ErrorUndefined, 0, NULL);
              mSignalledError = true;
              return;
          }
  
          ogg_buffer buf;
          ogg_reference ref;
          oggpack_buffer bits;
  
          makeBitReader(
                  (const uint8_t *)data + 7, size - 7,
                  &buf, &ref, &bits);
  
          if (mInputBufferCount == 0) {
              CHECK(mVi == NULL);
              mVi = new vorbis_info;
              vorbis_info_init(mVi);
  
              int ret = _vorbis_unpack_info(mVi, &bits);
              if (ret != 0) {
                  notify(OMX_EventError, OMX_ErrorUndefined, ret, NULL);
                  mSignalledError = true;
                  return;
              }
          } else {
              int ret = _vorbis_unpack_books(mVi, &bits);
              if (ret != 0) {
                  notify(OMX_EventError, OMX_ErrorUndefined, ret, NULL);
                  mSignalledError = true;
                  return;
              }
  
              CHECK(mState == NULL);
              mState = new vorbis_dsp_state;
              CHECK_EQ(0, vorbis_dsp_init(mState, mVi));
  
              notify(OMX_EventPortSettingsChanged, 1, 0, NULL);
              mOutputPortSettingsChange = AWAITING_DISABLED;
          }
  
          inQueue.erase(inQueue.begin());
          info->mOwnedByUs = false;
          notifyEmptyBufferDone(header);
  
          ++mInputBufferCount;
  
          return;
      }
  
      while ((!inQueue.empty() || (mSawInputEos && !mSignalledOutputEos)) && !outQueue.empty()) {
          BufferInfo *inInfo = NULL;
          OMX_BUFFERHEADERTYPE *inHeader = NULL;
          if (!inQueue.empty()) {
              inInfo = *inQueue.begin();
              inHeader = inInfo->mHeader;
          }
  
          BufferInfo *outInfo = *outQueue.begin();
          OMX_BUFFERHEADERTYPE *outHeader = outInfo->mHeader;
  
          int32_t numPageSamples = 0;
  
          if (inHeader) {
              if (inHeader->nFlags & OMX_BUFFERFLAG_EOS) {
                  mSawInputEos = true;
              }
  
              if (inHeader->nFilledLen || !mSawInputEos) {
                  if (inHeader->nFilledLen < sizeof(numPageSamples)) {
                      notify(OMX_EventError, OMX_ErrorBadParameter, 0, NULL);
                      mSignalledError = true;
                      ALOGE("onQueueFilled, input header has nFilledLen %u, expected %zu",
                              inHeader->nFilledLen, sizeof(numPageSamples));
                      return;
                  }
                  memcpy(&numPageSamples,
                         inHeader->pBuffer
                          + inHeader->nOffset + inHeader->nFilledLen - 4,
                         sizeof(numPageSamples));
  
                  if (inHeader->nOffset == 0) {
                      mAnchorTimeUs = inHeader->nTimeStamp;
                      mNumFramesOutput = 0;
                  }
  
                  inHeader->nFilledLen -= sizeof(numPageSamples);;
              }
          }
  
          if (numPageSamples >= 0) {
              mNumFramesLeftOnPage = numPageSamples;
          }
  
          ogg_buffer buf;
          buf.data = inHeader ? inHeader->pBuffer + inHeader->nOffset : NULL;
          buf.size = inHeader ? inHeader->nFilledLen : 0;
          buf.refcount = 1;
          buf.ptr.owner = NULL;
  
          ogg_reference ref;
          ref.buffer = &buf;
          ref.begin = 0;
          ref.length = buf.size;
          ref.next = NULL;
  
          ogg_packet pack;
          pack.packet = &ref;
          pack.bytes = ref.length;
          pack.b_o_s = 0;
          pack.e_o_s = 0;
          pack.granulepos = 0;
          pack.packetno = 0;
  
          int numFrames = 0;
  
          outHeader->nFlags = 0;
          int err = vorbis_dsp_synthesis(mState, &pack, 1);
          if (err != 0) {
              // FIXME temporary workaround for log spam
  #if !defined(__arm__) && !defined(__aarch64__)
              ALOGV("vorbis_dsp_synthesis returned %d", err);
  #else
              ALOGW("vorbis_dsp_synthesis returned %d", err);
  #endif
          } else {
              size_t numSamplesPerBuffer = kMaxNumSamplesPerBuffer;
              if (numSamplesPerBuffer > outHeader->nAllocLen / sizeof(int16_t)) {
                  numSamplesPerBuffer = outHeader->nAllocLen / sizeof(int16_t);
                  android_errorWriteLog(0x534e4554, "27833616");
              }
              numFrames = vorbis_dsp_pcmout(
                      mState, (int16_t *)outHeader->pBuffer,
                      (numSamplesPerBuffer / mVi->channels));
  
              if (numFrames < 0) {
                  ALOGE("vorbis_dsp_pcmout returned %d", numFrames);
                  numFrames = 0;
              }
          }
  
          if (mNumFramesLeftOnPage >= 0) {
              if (numFrames > mNumFramesLeftOnPage) {
                  ALOGV("discarding %d frames at end of page",
                       numFrames - mNumFramesLeftOnPage);
                  numFrames = mNumFramesLeftOnPage;
                  if (mSawInputEos) {
                      outHeader->nFlags = OMX_BUFFERFLAG_EOS;
                      mSignalledOutputEos = true;
                  }
              }
              mNumFramesLeftOnPage -= numFrames;
          }
  
          outHeader->nFilledLen = numFrames * sizeof(int16_t) * mVi->channels;
          outHeader->nOffset = 0;
  
          outHeader->nTimeStamp =
              mAnchorTimeUs
                  + (mNumFramesOutput * 1000000ll) / mVi->rate;
  
          mNumFramesOutput += numFrames;
  
          if (inHeader) {
              inInfo->mOwnedByUs = false;
              inQueue.erase(inQueue.begin());
              inInfo = NULL;
              notifyEmptyBufferDone(inHeader);
              inHeader = NULL;
          }
  
          outInfo->mOwnedByUs = false;
          outQueue.erase(outQueue.begin());
          outInfo = NULL;
          notifyFillBufferDone(outHeader);
          outHeader = NULL;
  
          ++mInputBufferCount;
      }
  }
  
  void SoftVorbis::onPortFlushCompleted(OMX_U32 portIndex) {
      if (portIndex == 0 && mState != NULL) {
          // Make sure that the next buffer output does not still
          // depend on fragments from the last one decoded.
  
          mNumFramesOutput = 0;
          mSawInputEos = false;
          mSignalledOutputEos = false;
          mNumFramesLeftOnPage = -1;
          vorbis_dsp_restart(mState);
      }
  }
  
  void SoftVorbis::onReset() {
      mInputBufferCount = 0;
      mNumFramesOutput = 0;
      if (mState != NULL) {
          vorbis_dsp_clear(mState);
          delete mState;
          mState = NULL;
      }
  
      if (mVi != NULL) {
          vorbis_info_clear(mVi);
          delete mVi;
          mVi = NULL;
      }
  
      mSawInputEos = false;
      mSignalledOutputEos = false;
      mSignalledError = false;
      mOutputPortSettingsChange = NONE;
  }
  
  void SoftVorbis::onPortEnableCompleted(OMX_U32 portIndex, bool enabled) {
      if (portIndex != 1) {
          return;
      }
  
      switch (mOutputPortSettingsChange) {
          case NONE:
              break;
  
          case AWAITING_DISABLED:
          {
              CHECK(!enabled);
              mOutputPortSettingsChange = AWAITING_ENABLED;
              break;
          }
  
          default:
          {
              CHECK_EQ((int)mOutputPortSettingsChange, (int)AWAITING_ENABLED);
              CHECK(enabled);
              mOutputPortSettingsChange = NONE;
              break;
          }
      }
  }
  
  }  // namespace android
  
  android::SoftOMXComponent *createSoftOMXComponent(
          const char *name, const OMX_CALLBACKTYPE *callbacks,
          OMX_PTR appData, OMX_COMPONENTTYPE **component) {
      return new android::SoftVorbis(name, callbacks, appData, component);
  }
  