Android4.2.2的Stagefright中编解码器数据流的维护

本文均属自己阅读源码的点滴总结,转账请注明出处谢谢。

欢迎和大家交流。qq:1037701636 email:[email protected]

Android源码版本Version:4.2.2; 硬件平台 全志A31

前沿:

在前面的博文中,基本提到的是stagefright相关的控制流,具体分析了android架构中的MediaExtractor、AwesomePlayer、StagefrightPlayer、OMXCodec等的创建,底层OMXNodinstance实例的创建。分析了OMX最底层插件库、编解码器组件的架构以及如何创建属于我们自己的OMX Plugin。

分析源码架构的另一个关键是数据流的分析,从这里开始,我们将对stagefright中的编解码缓存区进行分析:

1.

回到OMXCodec的创建过程的源码:

status_t AwesomePlayer::initVideoDecoder(uint32_t flags) {
.......
    mVideoSource = OMXCodec::Create(
            mClient.interface(), mVideoTrack->getFormat(),//提取视频流的格式, mClient:BpOMX;mVideoTrack->getFormat()
            false, // createEncoder,不创建编码器false
            mVideoTrack,
            NULL, flags, USE_SURFACE_ALLOC ? mNativeWindow : NULL);//创建一个解码器mVideoSource

    if (mVideoSource != NULL) {
        int64_t durationUs;
        if (mVideoTrack->getFormat()->findInt64(kKeyDuration, &durationUs)) {
            Mutex::Autolock autoLock(mMiscStateLock);
            if (mDurationUs < 0 || durationUs > mDurationUs) {
                mDurationUs = durationUs;
            }
        }

        status_t err = mVideoSource->start();//启动解码器OMXCodec,完成解码器的init初始化操作
.............
}

Android4.2.2下Stagefright多媒体架构中的A31的OMX插件和Codec组件博文我们对于OMXCodec::create已经做了详细的分析,这里来关注mVideoSource->start的相关功能,即OMXCodec::start的处理:

status_t OMXCodec::start(MetaData *meta) {
    Mutex::Autolock autoLock(mLock);
........
    return init();//进行初始化操作
}

这里调用init()的过程,将会进行buffer的申请操作,为后续的流操作打下基础:

status_t OMXCodec::init() {
    // mLock is held.
.........
    err = allocateBuffers();//缓存区的分配
    if (err != (status_t)OK) {
        return err;
    }

    if (mQuirks & kRequiresLoadedToIdleAfterAllocation) {
        err = mOMX->sendCommand(mNode, OMX_CommandStateSet, OMX_StateIdle);
        CHECK_EQ(err, (status_t)OK);

        setState(LOADED_TO_IDLE);
    }
............
}

我们来看allocateBuffers的实现

2.关注allocateBuffersOnPort的实现

status_t OMXCodec::allocateBuffers() {
    status_t err = allocateBuffersOnPort(kPortIndexInput);//输入缓存input口分配

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

    return allocateBuffersOnPort(kPortIndexOutput);//输出缓存input口分配
}

这里分别将对输入和输出口进行Buffer的申请与分配,对于解码器,需要输入口来存储待解码的数据源,需要将解码后的数据源存储到输出口,而这也符合硬件的实现逻辑。以输入缓存区分配为例展开分析:

status_t OMXCodec::allocateBuffersOnPort(OMX_U32 portIndex) {
.......
    OMX_PARAM_PORTDEFINITIONTYPE def;
    InitOMXParams(&def);
    def.nPortIndex = portIndex;//输入口

    err = mOMX->getParameter(
            mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));//获取输入口参数到def
..........
                err = mOMX->allocateBuffer(
                        mNode, portIndex, def.nBufferSize, &buffer,
                        &info.mData);
........
      info.mBuffer = buffer;//获取对应的buffer_id,有保存有底层的buffer的相关信息
        info.mStatus = OWNED_BY_US;
        info.mMem = mem;
        info.mMediaBuffer = NULL;
 ...........
        mPortBuffers[portIndex].push(info);//把当前的buffer恢复到mPortBuffers[2]中去

上述过程主要分为:

step1:先是获取底层解码器组件的当前的参数熟悉,一般这些参数都在建立OMX_Codec时完成的初始配置,前一博文中已经提到过。

step2:进行allocateBuffer的处理,这个函数的调用最终交给底层的OMX组件来完成,相关的实现将集成到A31的底层OMX编解码组件的处理流中进行分析。

step3:完成对分配好的buffer信息info,维护在mPortBuffers[0]这个端口中。

上述过程完成了输入与输出的Buffer分配,为后续解码操作buffer打下了基础。

3.mediaplay启动播放器

通过start的API调用,进入MediaplayerService::Client,再依次经过stagefrightplayer,AwesomePlayer。触发play的videoevent的发生.

void AwesomePlayer::postVideoEvent_l(int64_t delayUs) {
    ATRACE_CALL();

    if (mVideoEventPending) {
        return;
    }

    mVideoEventPending = true;
    mQueue.postEventWithDelay(mVideoEvent, delayUs < 0 ? 10000 : delayUs);
}

根据前一博文的分析可知,该事件对应的处理函数为AwesomePlayer::onVideoEvent(),该部分代码量较大,提取核心内容read的处理进行分析:

   status_t err = mVideoSource->read(&mVideoBuffer, &options);//循环读数据实际的OMX_CODEC::read,读取到mVideoBuffer

read的核心是获取可以用于render的视频数据,这表明了read函数主要完成了从视频源读取元数据,并调用解码器完成解码生成可送显的数据。

4. read函数的实现

可以想象read函数的应该是一个比较复杂的过程,我们从OMX_Codec的read函数入手来分析:

status_t OMXCodec::read(
        MediaBuffer **buffer, const ReadOptions *options) {
    status_t err = OK;
    *buffer = NULL;

    Mutex::Autolock autoLock(mLock);

        drainInputBuffers();//buffer,填充数据源

        if (mState == EXECUTING) {
            // Otherwise mState == RECONFIGURING and this code will trigger
            // after the output port is reenabled.
            fillOutputBuffers();
        }
    }

...........
}

read的核心逻辑总结为drainInputBuffers()和fillOutputBuffers(),我们对其依次进行深入的分析

5. drainInputBuffers()读取待解码的视频数据源到解码器的Inport

这里贴出其较为复杂的处理过程代码,主要分为以下3个部分进行分析:

(1)

bool OMXCodec::drainInputBuffer(BufferInfo *info) {
   if (mCodecSpecificDataIndex < mCodecSpecificData.size()) {
        CHECK(!(mFlags & kUseSecureInputBuffers));

        const CodecSpecificData *specific =
            mCodecSpecificData[mCodecSpecificDataIndex];

        size_t size = specific->mSize;

        if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mMIME)
                && !(mQuirks & kWantsNALFragments)) {
            static const uint8_t kNALStartCode[4] =
                    { 0x00, 0x00, 0x00, 0x01 };

            CHECK(info->mSize >= specific->mSize + 4);

            size += 4;

            memcpy(info->mData, kNALStartCode, 4);
            memcpy((uint8_t *)info->mData + 4,
                   specific->mData, specific->mSize);
        } else {
            CHECK(info->mSize >= specific->mSize);
            memcpy(info->mData, specific->mData, specific->mSize);//copy前面的数据字段
        }

        mNoMoreOutputData = false;

        CODEC_LOGV("calling emptyBuffer with codec specific data");

        status_t err = mOMX->emptyBuffer(
                mNode, info->mBuffer, 0, size,
                OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
                0);//处理buffer
        CHECK_EQ(err, (status_t)OK);

        info->mStatus = OWNED_BY_COMPONENT;

        ++mCodecSpecificDataIndex;
        return true;
    }

...............(1)

这部分的内容主要是提取一部分解码器字段,填充到info->mData的存储空间中去。这部分主要基于视频源的格式,如mp4等在创建OXMCodec病configureCodec时就完成了这个mCodecSpecificData字段的添加,应该些解码需要的特殊字段吧。是否需要要看其视频源的格式。获取完这个字段信息后就是正式读取视频源的数据了。

(2)

  for (;;) {
        MediaBuffer *srcBuffer;
        if (mSeekTimeUs >= 0) {
            if (mLeftOverBuffer) {
                mLeftOverBuffer->release();
                mLeftOverBuffer = NULL;
            }

            MediaSource::ReadOptions options;
            options.setSeekTo(mSeekTimeUs, mSeekMode);

            mSeekTimeUs = -1;
            mSeekMode = ReadOptions::SEEK_CLOSEST_SYNC;
            mBufferFilled.signal();

            err = mSource->read(&srcBuffer, &options);//读取视频源中的真实数据这里是MPEG4Source的read

            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;
                }
            }
        } else if (mLeftOverBuffer) {
            srcBuffer = mLeftOverBuffer;
            mLeftOverBuffer = NULL;

            err = OK;
        } else {
            err = mSource->read(&srcBuffer);
        }

        if (err != OK) {
            signalEOS = true;
            mFinalStatus = err;
            mSignalledEOS = true;
            mBufferFilled.signal();
            break;
        }

        if (mFlags & kUseSecureInputBuffers) {
            info = findInputBufferByDataPointer(srcBuffer->data());
            CHECK(info != NULL);
        }

        size_t remainingBytes = info->mSize - offset;//buffer中剩余的可以存储视频数据的空间

        if (srcBuffer->range_length() > remainingBytes) {//当前读取的数据已经达到解码的数据量
            if (offset == 0) {
                CODEC_LOGE(
                     "Codec‘s input buffers are too small to accomodate "
                     "buffer read from source (info->mSize = %d, srcLength = %d)",
                     info->mSize, srcBuffer->range_length());

                srcBuffer->release();
                srcBuffer = NULL;

                setState(ERROR);
                return false;
            }

            mLeftOverBuffer = srcBuffer;//把没读取的buffer记录下来
            break;
        }

        bool releaseBuffer = true;
        if (mFlags & kStoreMetaDataInVideoBuffers) {
                releaseBuffer = false;
                info->mMediaBuffer = srcBuffer;
        }

        if (mFlags & kUseSecureInputBuffers) {
                // Data in "info" is already provided at this time.

                releaseBuffer = false;

                CHECK(info->mMediaBuffer == NULL);
                info->mMediaBuffer = srcBuffer;
        } else {
            CHECK(srcBuffer->data() != NULL) ;
            memcpy((uint8_t *)info->mData + offset,
                    (const uint8_t *)srcBuffer->data()
                        + srcBuffer->range_offset(),
                    srcBuffer->range_length());//copy数据源数据到输入缓存,数据容量srcBuffer->range_length()
        }

        int64_t lastBufferTimeUs;
        CHECK(srcBuffer->meta_data()->findInt64(kKeyTime, &lastBufferTimeUs));
        CHECK(lastBufferTimeUs >= 0);
        if (mIsEncoder && mIsVideo) {
            mDecodingTimeList.push_back(lastBufferTimeUs);
        }

        if (offset == 0) {
            timestampUs = lastBufferTimeUs;
        }

        offset += srcBuffer->range_length();//增加偏移量

        if (!strcasecmp(MEDIA_MIMETYPE_AUDIO_VORBIS, mMIME)) {
            CHECK(!(mQuirks & kSupportsMultipleFramesPerInputBuffer));
            CHECK_GE(info->mSize, offset + sizeof(int32_t));

            int32_t numPageSamples;
            if (!srcBuffer->meta_data()->findInt32(
                        kKeyValidSamples, &numPageSamples)) {
                numPageSamples = -1;
            }

            memcpy((uint8_t *)info->mData + offset,
                   &numPageSamples,
                   sizeof(numPageSamples));

            offset += sizeof(numPageSamples);
        }

        if (releaseBuffer) {
            srcBuffer->release();
            srcBuffer = NULL;
        }

        ++n;

        if (!(mQuirks & kSupportsMultipleFramesPerInputBuffer)) {
            break;
        }

        int64_t coalescedDurationUs = lastBufferTimeUs - timestampUs;

        if (coalescedDurationUs > 250000ll) {
            // Don‘t coalesce more than 250ms worth of encoded data at once.
            break;
        }
    }...........

该部分是提取视频源数据的关键,主要通过 err = mSource->read(&srcBuffer, &options)来完成,mSource是在创建编解码器传入的,实际是一个对应于视频源格式的一个解析器MediaExtractor。比如在建立MP4的解析器MPEG4Extractor,通过新建一个new MPEG4Source。故最终这里调用的是MPEG4Source的read成员函数,其实际也维护着整个待解码的原始视频流。

我们可以看大在read函数后,会将待解码的数据流以for循环依次读入到底层的buffer空间中,只有当满足当前读取的原始数据片段比底层的input口的buffer剩余空间小srcBuffer->range_length() > remainingBytes,那就可以继续读取,否则直接break后,去进行下一步操作。或者如果一次待解码的数据时张是大于250ms也直接跳出。

这处理体现了处理的高效性。最终视频原始数据存储在info->mData的底层输入空间中。

(3)

    err = mOMX->emptyBuffer(
            mNode, info->mBuffer, 0, offset,
            flags, timestampUs);

触发底层的解码器组件进行处理。这部分留在后续对A31的底层编解码API操作时进行分析。

6.fillOutputBuffers对输出buffer口的填充,即实现解码过程:

void OMXCodec::fillOutputBuffers() {
    CHECK_EQ((int)mState, (int)EXECUTING);
...........
     Vector<BufferInfo> *buffers = &mPortBuffers[kPortIndexOutput];输出端口
    for (size_t i = 0; i < buffers->size(); ++i) {
        BufferInfo *info = &buffers->editItemAt(i);
        if (info->mStatus == OWNED_BY_US) {
            fillOutputBuffer(&buffers->editItemAt(i));
        }
    }
}
void OMXCodec::fillOutputBuffer(BufferInfo *info) {
    CHECK_EQ((int)info->mStatus, (int)OWNED_BY_US);

    if (mNoMoreOutputData) {
        CODEC_LOGV("There is no more output data available, not "
             "calling fillOutputBuffer");
        return;
    }

    CODEC_LOGV("Calling fillBuffer on buffer %p", info->mBuffer);
    status_t err = mOMX->fillBuffer(mNode, info->mBuffer);

    if (err != OK) {
        CODEC_LOGE("fillBuffer failed w/ error 0x%08x", err);

        setState(ERROR);
        return;
    }

    info->mStatus = OWNED_BY_COMPONENT;
}

从上面的代码看来,fillOutputBuffer的实现比drainInputBuffers简单了很多。但相同的是,两者最终都讲控制权交给底层的解码器来完成。

7.等待解码数据被fill到outbuffer中,OMXCodecObserver完成回调处理

等待解码完成的这部分内容在read函数中通过以下函数来实现:

    while (mState != ERROR && !mNoMoreOutputData && mFilledBuffers.empty()) {
        if ((err = waitForBufferFilled_l()) != OK) {//进入等待buffer被填充
            return err;
        }
    }

上述表明,只要mFilledBuffers为空则进入等待填充pthread_cond_timedwait。而这个线程被唤醒是通过底层的组件回调来完成的,回调函数的注册哎底层编解码器Node完成的,实际最终的回调是交给OMXCodecObserver来完成的:

struct OMXCodecObserver : public BnOMXObserver {
    OMXCodecObserver() {
    }

    void setCodec(const sp<OMXCodec> &target) {
        mTarget = target;
    }

    // from IOMXObserver
    virtual void onMessage(const omx_message &msg) {
        sp<OMXCodec> codec = mTarget.promote();

        if (codec.get() != NULL) {
            Mutex::Autolock autoLock(codec->mLock);
            codec->on_message(msg);//OMX_Codec的on_message处理
            codec.clear();
        }
    }

最终可以看到是由OMX_Codec->on_message来进行消息的处理,这部分的内容主要包括EMPTY_BUFFER_DONE和FILL_BUFFER_DONE两个message处理,对FILL_BUFFER_DONE完成后的消息回调进行分析:

void OMXCodec::on_message(const omx_message &msg) {
    if (mState == ERROR) {
        /*
         * only drop EVENT messages, EBD and FBD are still
         * processed for bookkeeping purposes
         */
        if (msg.type == omx_message::EVENT) {
            ALOGW("Dropping OMX EVENT message - we‘re in ERROR state.");
            return;
        }
    }

    switch (msg.type) {                                                                                                                                         case omx_message::FILL_BUFFER_DONE://底层回调callback告知当前                        ..............
                mFilledBuffers.push_back(i);//当前的输出buffer信息维护在mFilledBuffers
                mBufferFilled.signal();//发出信息用于渲染

可以看到这里对read线程进行了唤醒。

8.提取一个可用的解码后的数据帧

    size_t index = *mFilledBuffers.begin();
    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();//
    if (mSkipCutBuffer != NULL) {
        mSkipCutBuffer->submit(info->mMediaBuffer);
    }
    *buffer = info->mMediaBuffer;

获得了线程唤醒后的buffer,从这里获取到输出端口对应的Bufferinfo,作为最终的BufferInfo信息返回给read函数

9

经过5、6、7、8的处理过程,read最终返回可用于显示的mVideoBuffer,接下去就是如何送显的过程了。可以看到下面的代码,将会创建一个渲染器mVideoRenderer来完成这个解码后视频源的显示:


if ((mNativeWindow != NULL)
            && (mVideoRendererIsPreview || mVideoRenderer == NULL)) {//首次创建渲染器
        mVideoRendererIsPreview = false;

initRenderer_l();//初始化渲染器,新建一个AwesomeLocalRenderer
    }

if (mVideoRenderer != NULL) {
        mSinceLastDropped++;
        mVideoRenderer->render(mVideoBuffer);//启动渲染,即显示当前buffer
        if (!mVideoRenderingStarted) {
            mVideoRenderingStarted = true;
            notifyListener_l(MEDIA_INFO, MEDIA_INFO_RENDERING_START);
        }

}
void AwesomePlayer::initRenderer_l() {
    ATRACE_CALL();

    if (mNativeWindow == NULL) {
        return;
    }

    sp<MetaData> meta = mVideoSource->getFormat();

    int32_t format;
    const char *component;
    int32_t decodedWidth, decodedHeight;
    CHECK(meta->findInt32(kKeyColorFormat, &format));
    CHECK(meta->findCString(kKeyDecoderComponent, &component));
    CHECK(meta->findInt32(kKeyWidth, &decodedWidth));
    CHECK(meta->findInt32(kKeyHeight, &decodedHeight));

    int32_t rotationDegrees;
    if (!mVideoTrack->getFormat()->findInt32(
                kKeyRotation, &rotationDegrees)) {
        rotationDegrees = 0;
    }

    mVideoRenderer.clear();

    // Must ensure that mVideoRenderer‘s destructor is actually executed
    // before creating a new one.
    IPCThreadState::self()->flushCommands();

    // Even if set scaling mode fails, we will continue anyway
    setVideoScalingMode_l(mVideoScalingMode);
    if (USE_SURFACE_ALLOC
            && !strncmp(component, "OMX.", 4)
            && strncmp(component, "OMX.google.", 11)
            && strcmp(component, "OMX.Nvidia.mpeg2v.decode")) {//使用硬件渲染器,除去上述的解码器
        // Hardware decoders avoid the CPU color conversion by decoding
        // directly to ANativeBuffers, so we must use a renderer that
        // just pushes those buffers to the ANativeWindow.
        mVideoRenderer =
            new AwesomeNativeWindowRenderer(mNativeWindow, rotationDegrees);//一般是使用硬件渲染机制
    } else {
        // Other decoders are instantiated locally and as a consequence
        // allocate their buffers in local address space.  This renderer
        // then performs a color conversion and copy to get the data
        // into the ANativeBuffer.
        mVideoRenderer = new AwesomeLocalRenderer(mNativeWindow, meta);
    }
}

可以看到这里有2个渲染器的创建分支,OMX和OMX.google说明底层的解码器用的是软解码,那么他渲染器也使用所谓的本地渲染器实际是软渲染器。故这里我们使用的是AwesomeNativeWindowRenderer渲染器,其结构如下所述:

struct AwesomeNativeWindowRenderer : public AwesomeRenderer {
    AwesomeNativeWindowRenderer(
            const sp<ANativeWindow> &nativeWindow,
            int32_t rotationDegrees)
        : mNativeWindow(nativeWindow) {
        applyRotation(rotationDegrees);
    }

    virtual void render(MediaBuffer *buffer) {
        ATRACE_CALL();
        int64_t timeUs;
        CHECK(buffer->meta_data()->findInt64(kKeyTime, &timeUs));
        native_window_set_buffers_timestamp(mNativeWindow.get(), timeUs * 1000);
        status_t err = mNativeWindow->queueBuffer(
                mNativeWindow.get(), buffer->graphicBuffer().get(), -1);//直接使用queuebuffer进行渲染显示
        if (err != 0) {
            ALOGE("queueBuffer failed with error %s (%d)", strerror(-err),
                    -err);
            return;
        }

        sp<MetaData> metaData = buffer->meta_data();
        metaData->setInt32(kKeyRendered, 1);
    }

不是很复杂,只是实现了AwesomeRenderer渲染接口render。最终调用这个函数来实现对buffer的显示。这里看到很熟悉的queueBuffer,大家可以回看我的博文Android4.2.2 SurfaceFlinger之图形渲染queueBuffer实现和VSYNC的存在感,这是通过应用程序的本地窗口mNativeWindow(因为播放器videoview继承了sufaceview,surfaceview类会创建一个本地的surface,其继承了本地窗口类)将当前buffer提交给SurfaceFlinger服务进行显示,具体内容不在展开。

至此我们完成了stagefright下的编解码的数据流的相关操作,程序上复杂主要体现在emptybuffer和fillbuffer为主。当然由于能力有限,在很多细节上也没有进行很详细的分析,也希望大家多交流,多学习。

Android4.2.2的Stagefright中编解码器数据流的维护

时间: 2024-08-24 16:41:45

Android4.2.2的Stagefright中编解码器数据流的维护的相关文章

Android4.2.2下Stagefright下OMX编解码器组件的控制流

本文均属自己阅读源代码的点滴总结.转账请注明出处谢谢. 欢迎和大家交流. qq:1037701636 email:[email protected] Android源代码版本号Version:4.2.2; 硬件平台 全志A31 之所以单独把这块内容提炼出来,在于其具备的一定的层次性,结构上具备统一性,API函数的设计须要实现OMX架构独有的接口. 1. 在上一博文Android4.2.2下Stagefright多媒体架构中的A31的OMX插件和Codec组件中我们提到.通过Binder架构,在M

Android4.2.2下Stagefright多媒体架构中的A31的OMX插件和Codec组件

本文均属自己阅读源码的点滴总结,转账请注明出处谢谢. 欢迎和大家交流.qq:1037701636 email: [email protected] 在前面的博文中提到,AwesomePlayer::onPrepareAsyncEvent()开始进行Codec解码器组件的获取以及创建,这里和大家分享. 1.以解码器实例作为切入点 status_t AwesomePlayer::initVideoDecoder(uint32_t flags) { ATRACE_CALL(); ...... ALOG

android4.0浏览器在eclipse中编译的步骤

工程源码: 注意: 如果下载已经修过的源码,只要进行3.4.8步骤就应该可以了. eclipse版本:adt-bundle-windows (Android Developer Tools Build: v21.0.0-519525) 1.从android4.0系统的代码中拷贝一份浏览器的代码,到本地文件夹,并重命名为BrowserEclipse: < android4.0源码>/package/apps/Browser  -->  <本地目录>/BrowserEclipse

在双向多对一关联关系中如果由两端维护关联的关系会出问题

在双向多对一关联关系中如果由两端维护关联的关系会出问题. 举例: Clazz 为一端,Student为多端.多端的inverse 属性值为"false", 也就是说此时关联关系由两端维护.那么在保存的时候,就会出现一个问题:多端的外键列的所有值总是等于最新保存的一端的主键.

HDFS中的数据流

博客内容来自我啃的Hadoop权威指南,记录一下帮助自己理一下思路 一.文件读取 步骤1,客户端通过调用 FileSystem 对象的 open() 方法来打开想读取的文件,对于 HDFS 来说,这个对象是分布式文件系统(DistributedFileSystem)的一个实例 步骤2,DistributedFileSystem 通过使用 RPC 来调用 namenode ,获取文件的存储位置,以确定文件起始块的位置.namenode 返回文件所有组成块的副本的 datanode 地址.并且这些

关于bash中的数据流重导向

由来: 当我们执行一个指令时,可能会访问档案和执行一些讯息后将一些信息显示到屏幕上,而重定向就是将如此的的讯息导向到其他文件中,事实上,默认的其实就是数据导向到屏幕文件,只是将其重定向到你指定的文件中 输入输出方式: 标准输入 (stdin):代码为0,使用<(覆盖标准输入)或<<(累加标准输入) 标准输出 (stdout):代码为1,使用>(覆盖式标准输出)或>>(累加式标准输出) 标准错误输出 (stderr):代码为2,使用2>(覆盖式标准错误输出)或2&g

ZeroMQ(java)中的数据流SessionBase与SocketBase

前面的文章中已经比较的清楚了ZeroMQ(java)中如何在底层处理IO, 通过StreamEngine对象来维护SelectableChannel对象以及IO的事件回调,然后通过Poller对象来维护Selector对象,然后用IOObject对象来具体的管理SelectableChannel对象在Poller上面的注册,以及事件回调,他们之间的关系可以用下面的图形来简单的描述一下: 对于接收到的数据,首先由StreamEngine进行处理,其实它会调用内部的decoder将字节数据转化为Ms

从request中读数据流

ServletInputStream servletInputStream = reqeust.getInputStream(); int len=0; int size=reqeust.getContentLength(); byte[] read =new byte[size]; byte[] inb =new byte[size]; int readLea=0; while(len<size){ readLea=servletInputStream.read(read); for(int

工作中的oracle常用维护

http://blog.csdn.net/lili72 背景:由于历史原因,公司部分业务还在使用oracle中.一出问题就是搞的很麻烦.由于长期使用的是mysql 和hive,Hbase等,一些oracle的命令慢慢的也是忘记了,但是公司某些在oracle上的业务,突然出现问题,每次查找问题都查得比较费劲,现把常用命令记录下来. 常遇到的oracle的问题: 1.0 远程登录到oracle. 1.0.1  ssh  root@192.168.119.132 -p22330 su  oracle