Android视频播放数据读取的流程

转自 http://blog.sina.com.cn/foreverlovelost

这里分析Android4.0.1本地视频数据读取的流程,其他过程类似

当播放条件准备妥当之后,就要循环进行读取视频的原始数据放到MediaBuffer,将MediaBuffer中的数据输送到解码器中解码,解码后的数据放到MediaBuffer中,在将这MediaBuffer中的数据进行render显示。

本文主要侧重读取原始数据的流程,主要是代码跟踪,不夹杂个人分析,有些mpeg4的算法不懂。

1:onVideoEvent中开始读取数据,具体代码如下:
void AwesomePlayer::onVideoEvent() {
    if (!mVideoBuffer) {
        MediaSource::ReadOptions options;
        if (mSeeking != NO_SEEK) {
            LOGV("seeking to %lld us (%.2f secs)", mSeekTimeUs, mSeekTimeUs /1E6);

            options.setSeekTo(
                    mSeekTimeUs,
                    mSeeking == SEEK_VIDEO_ONLY
                        ?MediaSource::ReadOptions::SEEK_NEXT_SYNC
                        :MediaSource::ReadOptions::SEEK_CLOSEST_SYNC);
        }
        for (;;) {
              status_terr =mVideoSource->read(&mVideoBuffer,&options);
        }
    }
}
蓝色为核心代码,如果是正常读取,options为null,否则这个结构体中包含了seek到的时间和seek的模式,用于具体从文件中哪里开始读取,传入的mVideoBuffer引用用来装解码后的数据

2:蓝色部分调用的是OMXCodec::read函数,这个函数中核心的代码如下:
status_t OMXCodec::read(
        MediaBuffer **buffer, const ReadOptions*options) {
    status_t err = OK;
    *buffer = NULL;
    bool seeking = false;
    int64_t seekTimeUs;
    ReadOptions::SeekModeseekMode;
    if (options&&options->getSeekTo(&seekTimeUs,&seekMode)) {
        seeking = true;
    }

    if (seeking) {
        CODEC_LOGV("seeking to %lld us (%.2f secs)",seekTimeUs, seekTimeUs / 1E6);
        CHECK(seekTimeUs >= 0);
        mSeekTimeUs = seekTimeUs;
        mSeekMode = seekMode;
    }

      drainInputBuffers();
     
      size_t index = *mFilledBuffers.begin(); // A list of indices intomPortStatus[kPortIndexOutput] filled with data.
    mFilledBuffers.erase(mFilledBuffers.begin());

    BufferInfo *info =&mPortBuffers[kPortIndexOutput].editItemAt(index);
    CHECK_EQ((int)info->mStatus,(int)OWNED_BY_US);
    info->mStatus= OWNED_BY_CLIENT;

    info->mMediaBuffer->add_ref();
      *buffer = info->mMediaBuffer;

    return OK;
}
两点:
a, drainInputBuffers开始了数据的读取;
b, mFilledBuffers从这个队列中读取已经解码后的数据放入到传入的MediaBuffer中, mFilledBuffers队列中的MediaBuffer就是 drainInputBuffers中写进去的

3:跟到drainInputBuffer中看看
bool OMXCodec::drainInputBuffer(BufferInfo *info) {
    CODEC_LOGV("callingemptyBuffer with codec specific data");
      status_t err =mOMX->emptyBuffer(
              mNode,info->mBuffer, 0, size,
             OMX_BUFFERFLAG_ENDOFFRAME |OMX_BUFFERFLAG_CODECCONFIG,
             0);
    CHECK_EQ(err,(status_t)OK);
    info->mStatus= OWNED_BY_COMPONENT;

    status_t err;
    bool signalEOS = false;
    int64_t timestampUs =0;
    size_t offset = 0;
    int32_t n = 0;

    for (;;) {
        MediaBuffer *srcBuffer;
        if (mSeekTimeUs >= 0) {
            MediaSource::ReadOptions options;
            options.setSeekTo(mSeekTimeUs, mSeekMode);

            mSeekTimeUs = -1;
            mSeekMode= ReadOptions::SEEK_CLOSEST_SYNC;
             err =mSource->read(&srcBuffer,&options);

            if (err ==OK) {
                int64_t targetTimeUs;
                if(srcBuffer->meta_data()->findInt64(
                            kKeyTargetTime,&targetTimeUs)
                        && targetTimeUs >=0) {
                    CODEC_LOGV("targetTimeUs = %lld us",targetTimeUs);
                    mTargetTimeUs = targetTimeUs;
                } else {
                    mTargetTimeUs = -1;
                }
            }
        }
}
       
    CODEC_LOGV("CallingemptyBuffer on buffer %p (length %d), "
                "timestamp %lld us (%.2fsecs)",
                info->mBuffer,offset,
                timestampUs, timestampUs /1E6);
      err = mOMX->emptyBuffer(
          mNode, info->mBuffer, 0,offset,
          flags, timestampUs);
    info->mStatus= OWNED_BY_COMPONENT;
    return true;
}
两点:
a,调用 err= mSource->read(&srcBuffer,&options);从原始文件中读取原始数据,
b,往srcBuffer中读取数据前后,都调用omx转移已经读取到该info中的数据,目的是解码,解码后的数据就房子了 mFilledBuffers这个队列中;

4:针对mpeg4类型的视频,上面的read函数调用的是MPEG4Source的read函数,核心代码如下:
status_t MPEG4Source::read(
       MediaBuffer **out, constReadOptions *options) {
    *out =NULL;

    int64_tseekTimeUs;
   ReadOptions::SeekMode mode;
    if(options &&options->getSeekTo(&seekTimeUs,&mode)) {
      
        if分支是用于有seek的流程
        1:首先找到seektime附近的sampleIndex;
        2:然后找到sampleIndex附近的关键帧的syncSampleIndex;
        3:然后syncSampleIndex找到具体的sampleTime,sampleTime就是目前需要播放到的位置;
        4:mSampleTable->getMetaDataForSample调用这个函数找到sampleTime时间的offset和size;
        5:有了offset和size之后剩下就是调用mDataSource->readAt(offset,(uint8_t *)mBuffer->data(),size);读取数据放到buffer中去了;
      
       uint32_t findFlags =0;
       switch (mode) {
          caseReadOptions::SEEK_PREVIOUS_SYNC:
              findFlags= SampleTable::kFlagBefore;
             break;
          case ReadOptions::SEEK_NEXT_SYNC:
              findFlags= SampleTable::kFlagAfter;
             break;
          caseReadOptions::SEEK_CLOSEST_SYNC:
          case ReadOptions::SEEK_CLOSEST:
              findFlags= SampleTable::kFlagClosest;
             break;
          default:
             CHECK(!"Should not be here.");
             break;
       }

       uint32_tsampleIndex;
       status_t err =mSampleTable->findSampleAtTime(
              seekTimeUs* mTimescale / 1000000,
             &sampleIndex, findFlags);

       uint32_tsyncSampleIndex;
       if (err == OK) {
           err =mSampleTable->findSyncSampleNear(
                 sampleIndex,&syncSampleIndex, findFlags);
       }

       uint32_tsampleTime;
       if (err == OK) {
           err =mSampleTable->getMetaDataForSample(
                 sampleIndex, NULL, NULL,&sampleTime);
       }

       if (mode ==ReadOptions::SEEK_CLOSEST) {
          targetSampleTimeUs = (sampleTime * 1000000ll) /mTimescale;
       }

       mCurrentSampleIndex =syncSampleIndex;
   }

    off64_toffset;
    size_tsize;
   uint32_t cts;
    boolisSyncSample;
    boolnewBuffer = false;
    if(mBuffer == NULL) {
       newBuffer =true;

       status_t err =
           mSampleTable->getMetaDataForSample(
                 mCurrentSampleIndex,&offset, &size,&cts, &isSyncSample);

       if (err != OK) {
          return err;
       }

       err =mGroup->acquire_buffer(&mBuffer);

       if (err != OK) {
          CHECK(mBuffer == NULL);
          return err;
       }
   }

    if(!mIsAVC || mWantsNALFragments) {
       if (newBuffer) {
           ssize_t num_bytes_read =
             mDataSource->readAt(offset, (uint8_t*)mBuffer->data(), size);

          CHECK(mBuffer != NULL);
          mBuffer->set_range(0,size);
         mBuffer->meta_data()->clear();
         mBuffer->meta_data()->setInt64(
                 kKeyTime, ((int64_t)cts *1000000) / mTimescale);

          if (isSyncSample) {
             mBuffer->meta_data()->setInt32(kKeyIsSyncFrame,1);
          }

          ++mCurrentSampleIndex;
       }

       if (!mIsAVC) {
          *out = mBuffer;
          mBuffer = NULL;

          return OK;
       }

       // Each NAL unit is split upinto its constituent fragments and
       // each one of them returnedin its own buffer.

      CHECK(mBuffer->range_length() >=mNALLengthSize);

       const uint8_t *src=
          (const uint8_t *)mBuffer->data()+ mBuffer->range_offset();

       size_t nal_size =parseNALSize(src);

       MediaBuffer *clone =mBuffer->clone();
       CHECK(clone !=NULL);
      clone->set_range(mBuffer->range_offset()+ mNALLengthSize, nal_size);

       CHECK(mBuffer !=NULL);
      mBuffer->set_range(
             mBuffer->range_offset() + mNALLengthSize +nal_size,
             mBuffer->range_length() - mNALLengthSize -nal_size);

       if(mBuffer->range_length() == 0) {
          mBuffer->release();
          mBuffer = NULL;
       }

       *out = clone;

       return OK;
   }
}
蓝色部分为主要的流程

5:后续就是开始调用SampleTable.cpp和SampleIterator.cpp这两个类的相关函数解析文件和读取数据,最主要的函数时通过sampleIndex获取offset和size信息了,代码如下:
status_t SampleTable::getMetaDataForSample(
        uint32_t sampleIndex,
        off64_t *offset,
        size_t *size,
        uint32_t *compositionTime,
        bool *isSyncSample) {
    Mutex::AutolockautoLock(mLock);
    status_t err;
    if ((err =mSampleIterator->seekTo(sampleIndex)) != OK) {
        return err;
    }
    if (offset) {
        *offset =mSampleIterator->getSampleOffset();
    }

    if (size) {
        *size =mSampleIterator->getSampleSize();
    }
    if (compositionTime) {
        *compositionTime =mSampleIterator->getSampleTime();
    }
    if (isSyncSample) {
        *isSyncSample = false;
        if (mSyncSampleOffset < 0){
            // Everysample is a sync sample.
            *isSyncSample = true;
        } else {
            size_t i =(mLastSyncSampleIndex < mNumSyncSamples)
                    &&(mSyncSamples[mLastSyncSampleIndex] <=sampleIndex)
                ? mLastSyncSampleIndex :0;
            while (i< mNumSyncSamples &&mSyncSamples[i] < sampleIndex) {
                ++i;
            }
            if (i< mNumSyncSamples &&mSyncSamples[i] == sampleIndex) {
              *isSyncSample = true;
            }
            mLastSyncSampleIndex = i;
        }
    }
    return OK;
}
下面这个函数没有看懂,对具体的mpeg4压缩协议没有进行深入了解
status_tSampleIterator::findSampleTime(
       uint32_t sampleIndex,uint32_t *time) {
    if(sampleIndex >=mTable->mNumSampleSizes) {
       returnERROR_OUT_OF_RANGE;
   }

    while(sampleIndex >= mTTSSampleIndex + mTTSCount){
       if (mTimeToSampleIndex ==mTable->mTimeToSampleCount) {
          return ERROR_OUT_OF_RANGE;
       }

       mTTSSampleIndex +=mTTSCount;
       mTTSSampleTime += mTTSCount *mTTSDuration;

       mTTSCount =mTable->mTimeToSample[2 *mTimeToSampleIndex];
       mTTSDuration =mTable->mTimeToSample[2 * mTimeToSampleIndex +1];

      ++mTimeToSampleIndex;
   }

    *time =mTTSSampleTime + mTTSDuration * (sampleIndex -mTTSSampleIndex);

    *time+=mTable->getCompositionTimeOffset(sampleIndex);

    returnOK;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android视频播放流程可以简单分为以下几个步骤: 1.选择视频源:首先需要确定要播放视频文件的位置,可以是本地文件、网络文件或者通过其他方式获取的视频数据。 2.创建MediaPlayer对象:使用MediaPlayer类,通过其构造方法创建一个MediaPlayer对象。MediaPlayer是Android中用于播放和控制音视频资源的主要类。 3.设置数据源:在MediaPlayer对象上调用setDataSource()方法,将视频源传递给MediaPlayer,告诉它要播放的是哪个视频文件。 4.准备MediaPlayer:使用prepare()或prepareAsync()方法来准备MediaPlayer。prepare()方法是同步的,会阻塞当前线程,等待MediaPlayer准备就绪;而prepareAsync()方法是异步的,不会阻塞线程,会在后台准备MediaPlayer。 5.设置显示界面:如果需要在界面上显示视频,需要先获取一个SurfaceView或TextureView,并将其传递给MediaPlayer的setSurface()方法,用于渲染视频。 6.开始播放:通过MediaPlayer的start()方法开始播放视频。此时,MediaPlayer会从数据读取视频数据,解码并渲染到SurfaceView或TextureView上进行显示。 7.视频控制:可以通过调用MediaPlayer的其他方法,如pause()、stop()、seekTo()等,来控制视频的暂停、停止和进度跳转等操作。 8.释放资源:在不需要播放视频时,需要及时释放MediaPlayer的资源,可以调用release()方法来释放相关资源。 总的来说,Android视频播放流程包括选择视频源、创建MediaPlayer对象、设置数据源、准备MediaPlayer、设置显示界面、开始播放视频控制和释放资源等步骤。根据具体需求,可以根据这个基本流程进行扩展和定制。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值