OpenHarmony 3.2 Beta多媒体系列——音视频播放gstreamer

【鸿蒙南向开发实战】OpenHarmony之媒体组件模块简介 本文中我们讨论了OpenHarmony 3.2 Release中媒体组件模块的功能以及结构,并且对其实现的原理进行了一些简单的探讨。GStreamer是一个功能强大的多媒体库,上文中作为例子讨论的PlayerEngine使用的是一款"全自动"的pipeline。而OpenHarmony中也有像AVCodecEngine那种OpenHarmony化的pipeline,包含了依赖于OpenHarmony平台编解码能力的插件。涉及的内容不单单包括媒体组件跟GStreamer,还包括驱动相关的内容。 阅读详情

目录

一、 简介

二、 目录

三 、Gstreamer介绍

1. 简介

2.Gstreamer几个重要的概念

四、调用流程

五、源码分析

1. PrepareAsync分析

2. Play分析

六、总结


一、 简介

多媒体播放框架主要的实现在PlayerServer服务中,这个服务提供了媒体播放框架所需要的实现环境,继续跟踪代码分析发现,PlayerServer主要通过gstreamer适配层,对gstreamer进行调用。gstreamer属于更加具体的实现,所以本篇文章主要是分析PlayerServer通过适配层调用到gstreamer的过程。

此前,我在《OpenHarmony 3.2 Beta多媒体系列-音视频播放框架》一文中,主要分析了多媒体播放的框架层代码,本地接口通过服务端的proxy代理类进行IPC调用,最终调用到PlayerServer服务端。本篇主要分析了多媒体gstreamer的调用,涉及到从PlayerServer到gstreamer的整体流程。

二、 目录

  gstreamer
    ├── BUILD.gn
    ├── common
    │   ├── BUILD.gn
    │   ├── playbin_adapter
    │   │   ├── i_playbin_ctrler.h
    │   │   ├── playbin2_ctrler.cpp
    │   │   ├── playbin2_ctrler.h
    │   │   ├── playbin_ctrler_base.cpp
    │   │   ├── playbin_ctrler_base.h
    │   │   ├── playbin_msg_define.h
    │   │   ├── playbin_sink_provider.h
    │   │   ├── playbin_state.cpp
    │   │   ├── playbin_state.h
    │   │   ├── playbin_task_mgr.cpp
    │   │   └── playbin_task_mgr.h
    │   ├── state_machine
    │   │   ├── state_machine.cpp
    │   │   └── state_machine.h
    ├── factory
    │   ├── BUILD.gn
    │   └── engine_factory.cpp
    └── player
        ├── BUILD.gn
        ├── player_codec_ctrl.cpp
        ├── player_codec_ctrl.h
        ├── player_engine_gst_impl.cpp
        ├── player_engine_gst_impl.h
        ├── player_sinkprovider.cpp
        ├── player_sinkprovider.h
        ├── player_track_parse.cpp
        └── player_track_parse.h

目录主要是多媒体子系统中的engine部分,涉及到了gstreamer的适配层,gstreamer具体的实现是在third_party/gstreamer目录中。

三 、Gstreamer介绍

1. 简介

Gstreamer是一个跨平台的多媒体框架,应用程序可以通过管道(Pipeline)的方式,将多媒体处理的各个步骤串联起来,达到预期的效果。每个步骤通过元素(Element)基于GObject对象系统通过插件(plugins)的方式实现,方便了各项功能的扩展。

2.Gstreamer几个重要的概念

Element

Element是Gstreamer中最重要的对象类型之一。一个element实现一个功能(读取文件,解码,输出等),程序需要创建多个element,并按顺序将其串联起来,构成一个完整的Pipeline。

Pad

Pad是一个element的输入/输出接口,分为src pad(生产数据)和sink pad(消费数据)两种。

两个element必须通过pad才能连接起来,pad拥有当前element能处理数据类型的能力(capabilities),会在连接时通过比较src pad和sink pad中所支持的能力,来选择最恰当的数据类型用于传输,如果element不支持,程序会直接退出。在element通过pad连接成功后,数据会从上一个element的src pad传到下一个element的sink pad然后进行处理。

Bin和Pipeline

Bin是一个容器,用于管理多个element,改变bin的状态时,bin会自动去修改所包含的element的状态,也会转发所收到的消息。如果没有bin,我们需要依次操作我们所使用的element。通过bin降低了应用的复杂度。

Pipeline继承自bin,为程序提供一个bus用于传输消息,并且对所有子element进行同步。当将Pipeline的状态设置为PLAYING时,Pipeline会在一个/多个新的线程中通过element处理数据。

四、调用流程

五、源码分析

1. PrepareAsync分析

首先,在PlayerServer的PrepareAsync中会调用OnPrepare(false),具体是在OnPrepare(false)中实现,参数传入false,表明调用的是异步方法。

int32_t PlayerServer::PrepareAsync()
{
    std::lock_guard<std::mutex> lock(mutex_);
    MEDIA_LOGW("KPI-TRACE: PlayerServer PrepareAsync in");

    if (lastOpStatus_ == PLAYER_INITIALIZED || lastOpStatus_ == PLAYER_STOPPED) {
        return OnPrepare(false);
    } else {
        MEDIA_LOGE("Can not Prepare, currentState is %{public}s", GetStatusDescription(lastOpStatus_).c_str());
        return MSERR_INVALID_OPERATION;
    }
}

OnPrepare方法中,先通过playerEngine_调用SerVideoSurface的方法,将surface_设置到PlayerEngineGstImpl中(producerSurface_),接着启动一个任务,调用目前状态的Prepare()方法。

int32_t PlayerServer::OnPrepare(bool sync)
{
    CHECK_AND_RETURN_RET_LOG(playerEngine_ != nullptr, MSERR_NO_MEMORY, "playerEngine_ is nullptr");
    int32_t ret = MSERR_OK;

#ifdef SUPPORT_VIDEO
    if (surface_ != nullptr) {
        ret = playerEngine_->SetVideoSurface(surface_);
        CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, MSERR_INVALID_OPERATION, "Engine SetVideoSurface Failed!");
    }
#endif

    lastOpStatus_ = PLAYER_PREPARED;

    auto preparedTask = std::make_shared<TaskHandler<int32_t>>([this]() {
        MediaTrace::TraceBegin("PlayerServer::PrepareAsync", FAKE_POINTER(this));
        auto currState = std::static_pointer_cast<BaseState>(GetCurrState());
        return currState->Prepare();
    });

    ret = taskMgr_.LaunchTask(preparedTask, PlayerServerTaskType::STATE_CHANGE);
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, ret, "Prepare launch task failed");

    if (sync) {
        (void)preparedTask->GetResult(); // wait HandlePrpare
    }
    return MSERR_OK;
}

进入Preparing状态后,会触发PlayerServer的HandlePrepare()方法被调用,在这个方法里会通过playerEngine_调用PrepareAsync方法,这个方法调用的是PlayerEngineGstImpl对应的PrepareAsync方法。

int32_t PlayerServer::HandlePrepare()
{
    int32_t ret = playerEngine_->PrepareAsync();
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, MSERR_INVALID_OPERATION, "Server Prepare Failed!");
    if (config_.leftVolume <= 1.0f || config_.rightVolume <= 1.0f) {
        ret = playerEngine_->SetVolume(config_.leftVolume, config_.rightVolume);
        MEDIA_LOGD("Prepared SetVolume leftVolume:%{public}f rightVolume:%{public}f, ret:%{public}d", \
                   config_.leftVolume, config_.rightVolume, ret);
    }
    (void)playerEngine_->SetLooping(config_.looping);

    {
        auto rateTask = std::make_shared<TaskHandler<void>>([this]() {
            auto currState = std::static_pointer_cast<BaseState>(GetCurrState());
            (void)currState->SetPlaybackSpeed(config_.speedMode);
        });

        (void)taskMgr_.LaunchTask(rateTask, PlayerServerTaskType::RATE_CHANGE);
    }
    return MSERR_OK;
}

首先初始化playBinCtrler_,后续的操作都是通过PlayBinCtrlerBase对象来操作的,所以PlayBinCtrlerInit()方法会创建PlayBinCtrlerBase对象(playBinCtrler_),创建好以后通过playBinCtrler_进行SetSource和SetXXXListener的设置。

int32_t PlayerEngineGstImpl::PrepareAsync()
{
    std::unique_lock<std::mutex> lock(mutex_);
    MEDIA_LOGD("Prepare in");

    int32_t ret = PlayBinCtrlerInit();
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, MSERR_INVALID_VAL, "PlayBinCtrlerInit failed");

    CHECK_AND_RETURN_RET_LOG(playBinCtrler_ != nullptr, MSERR_INVALID_VAL, "playBinCtrler_ is nullptr");
    ret = playBinCtrler_->PrepareAsync();
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, ret, "PrepareAsync failed");

    // The duration of some resources without header information cannot be obtained.
    MEDIA_LOGD("Prepared ok out");
    return MSERR_OK;
}

初始化完成以后,接下来进行playBinCtrler_的PrepareAsync的调用,PlayBinCtrlerBase中的PrepareAsync的方法间接地调用了PrepareAsyncInternal。

int32_t PlayBinCtrlerBase::PrepareAsync()
{
    MEDIA_LOGD("enter");

    std::unique_lock<std::mutex> lock(mutex_);
    return PrepareAsyncInternal();
}

PrepareAsyncInternal首先判断当前的状态,如果是preparingState或preparedState,那么就直接返回成功,否则继续向下调用。接下来会调用EnterInitializedState(),这个方法中会创建playbin,设置signal的回调以及gstreamer参数的设置。最后调用目前状态的Prepare方法,此时的状态是InitializedState。

int32_t PlayBinCtrlerBase::PrepareAsyncInternal()
{
    if ((GetCurrState() == preparingState_) || (GetCurrState() == preparedState_)) {
        MEDIA_LOGI("already at preparing state, skip");
        return MSERR_OK;
    }

    CHECK_AND_RETURN_RET_LOG((!uri_.empty() || appsrcWrap_), MSERR_INVALID_OPERATION, "Set uri firsty!");

    int32_t ret = EnterInitializedState();
    CHECK_AND_RETURN_RET(ret == MSERR_OK, ret);

    auto currState = std::static_pointer_cast<BaseState>(GetCurrState());
    ret = currState->Prepare();
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, ret, "PrepareAsyncInternal failed");

    return MSERR_OK;
}

InitializedState的Prepare方法又通过ctrler_调回到PlayBinCtrlerBase的ChangeState方法,这个方法是在PlayBinCtrlerBase的父类StateMachine中,它是一个状态机,管理着各种状态的切换。

int32_t PlayBinCtrlerBase::InitializedState::Prepare()
{
    ctrler_.ChangeState(ctrler_.preparingState_);
    return MSERR_OK;
}

很多表示状态的类在PlayBinCtrlerBase中进行声明,这些子类的具体实现功能在playbin_state.cpp中。

private:
    class BaseState;
    class IdleState;
    class InitializedState;
    class PreparingState;
    class PreparedState;
    class PlayingState;
    class PausedState;
    class StoppedState;
    class StoppingState;
    class PlaybackCompletedState;

接下来看一下状态机的ChangeState方法,可以看出切换状态的时候,先调用切换前状态的StateExit()方法,再调用切换后状态的StateEnter()。如果需要一些操作,我们可以在状态的StateEnter和StateExit中进行。

void StateMachine::ChangeState(const std::shared_ptr<State> &state)
{
    ......
    if (currState_ != nullptr && currState_->GetStateName() == "stopping_state" && state->GetStateName() != "stopped_state") {
        return;
    }
    if (currState_) {
        currState_->StateExit();
    }
    currState_ = state;
    state->StateEnter();
}

因为上面切换状态调用的是ctrler_.ChangeState(ctrler_.preparingState_),所以接下来看一下PreparingState状态的StateEnter方法。这个方法中首先是调用了ctrler_.ReportMessage(msg),字面上看是用来上报msg信息的。

void PlayBinCtrlerBase::PreparingState::StateEnter()
{
    PlayBinMessage msg = { PLAYBIN_MSG_SUBTYPE, PLAYBIN_SUB_MSG_BUFFERING_START, 0, {} };
    ctrler_.ReportMessage(msg);

    GstStateChangeReturn ret;
    (void)ChangePlayBinState(GST_STATE_PAUSED, ret);

    MEDIA_LOGD("PreparingState::StateEnter finished");
}

ctrler_是PlayBinCtrlerBase类型的变量,直接看PlayBinCtrlerBase的ReportMessage方法,这个方法的核心,是创建一个任务后,将任务放入消息队列中,等待消息被处理,这里我们最想知道的是这个消息会在什么地方被处理。msgReportHandler创建了TaskHandler,这个里面会调用notifier_(msg),这里的notifier_比较重要,我们可以顺着这个变量向上分析。

void PlayBinCtrlerBase::ReportMessage(const PlayBinMessage &msg)
{
    ......
    auto msgReportHandler = std::make_shared<TaskHandler<void>>([this, msg]() { notifier_(msg); });
    int32_t ret = msgQueue_->EnqueueTask(msgReportHandler);
    if (ret != MSERR_OK) {
        MEDIA_LOGE("async report msg failed, type: %{public}d, subType: %{public}d, code: %{public}d",
                   msg.type, msg.subType, msg.code);
    };

    if (msg.type == PlayBinMsgType::PLAYBIN_MSG_EOS) {
        ProcessEndOfStream();
    }
}

notifier_是在PlayBinCtrlerBase被创建的时候赋值的。

PlayBinCtrlerBase::PlayBinCtrlerBase(const PlayBinCreateParam &createParam)
    : renderMode_(createParam.renderMode),
    notifier_(createParam.notifier),
    sinkProvider_(createParam.sinkProvider)
{
    MEDIA_LOGD("enter ctor, instance: 0x%{public}06" PRIXPTR "", FAKE_POINTER(this));
}

在源码分析的前期PlayerEngineGstImpl初始化PlayBinCtrlerBase的时候进行了创建notifier = std::bind(&PlayerEngineGstImpl::OnNotifyMessage, this, std::placeholders::_1) notifier相当于是调用了PlayerEngineGstImpl::OnNotifyMessage方法。所以上述中的处理函数就是PlayerEngineGstImpl::OnNotifyMessage。

int32_t PlayerEngineGstImpl::PlayBinCtrlerPrepare()
{
    uint8_t renderMode = IPlayBinCtrler::PlayBinRenderMode::DEFAULT_RENDER;
    auto notifier = std::bind(&PlayerEngineGstImpl::OnNotifyMessage, this, std::placeholders::_1);

    {
        std::unique_lock<std::mutex> lk(trackParseMutex_);
        sinkProvider_ = std::make_shared<PlayerSinkProvider>(producerSurface_);
        sinkProvider_->SetAppInfo(appuid_, apppid_);
    }

    IPlayBinCtrler::PlayBinCreateParam createParam = {
        static_cast<IPlayBinCtrler::PlayBinRenderMode>(renderMode), notifier, sinkProvider_
    };
    playBinCtrler_ = IPlayBinCtrler::Create(IPlayBinCtrler::PlayBinKind::PLAYBIN2, createParam);
    ......
    return MSERR_OK;
}

在OnNotifyMessage中指定了各种消息类型对应的执行函数,上述代码中创建的Message类型是PLAYBIN_MSG_SUBTYPE,子类型为PLAYBIN_SUB_MSG_BUFFERING_START。

void PlayerEngineGstImpl::OnNotifyMessage(const PlayBinMessage &msg)
{
    const std::unordered_map<int32_t, MsgNotifyFunc> MSG_NOTIFY_FUNC_TABLE = {
        { PLAYBIN_MSG_ERROR, std::bind(&PlayerEngineGstImpl::HandleErrorMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_SEEKDONE, std::bind(&PlayerEngineGstImpl::HandleSeekDoneMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_SPEEDDONE, std::bind(&PlayerEngineGstImpl::HandleInfoMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_BITRATEDONE, std::bind(&PlayerEngineGstImpl::HandleInfoMessage, this, std::placeholders::_1)},
        { PLAYBIN_MSG_EOS, std::bind(&PlayerEngineGstImpl::HandleInfoMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_STATE_CHANGE, std::bind(&PlayerEngineGstImpl::HandleInfoMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_SUBTYPE, std::bind(&PlayerEngineGstImpl::HandleSubTypeMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_AUDIO_SINK, std::bind(&PlayerEngineGstImpl::HandleAudioMessage, this, std::placeholders::_1) },
        { PLAYBIN_MSG_POSITION_UPDATE, std::bind(&PlayerEngineGstImpl::HandlePositionUpdateMessage, this,
            std::placeholders::_1) },
    };
    if (MSG_NOTIFY_FUNC_TABLE.count(msg.type) != 0) {
        MSG_NOTIFY_FUNC_TABLE.at(msg.type)(msg);
    }
}

最终的流程走到了PlayerEngineGstImpl::HandleBufferingStart(),在这个方法中,主要通过obs_将format传给IPlayerEngineObs的OnInfo方法。

void PlayerEngineGstImpl::HandleBufferingStart()
{
    percent_ = 0;
    Format format;
(void)format.PutIntValue(std::string(PlayerKeys::PLAYER_BUFFERING_START), 0);
    std::shared_ptr<IPlayerEngineObs> notifyObs = obs_.lock();
    if (notifyObs != nullptr) {
        notifyObs->OnInfo(INFO_TYPE_BUFFERING_UPDATE, 0, format);
    }
}

我们重点看一下obs_是哪里设置的,在PlayerServer的初始化InitPlayEngine。shared_from_this()相当于是把PlayerServer自身赋值给obs,PlayerServer也是实现了IPlayerEngineObs对应的接口。

int32_t PlayerServer::InitPlayEngine(const std::string &url)
{
    ......
    int32_t ret = taskMgr_.Init();
    auto engineFactory = EngineFactoryRepo::Instance().GetEngineFactory(IEngineFactory::Scene::SCENE_PLAYBACK, url);
    
    playerEngine_ = engineFactory->CreatePlayerEngine(appUid_, appPid_);

    if (dataSrc_ == nullptr) {
        ret = playerEngine_->SetSource(url);
    } else {
        ret = playerEngine_->SetSource(dataSrc_);
    }

    std::shared_ptr<IPlayerEngineObs> obs = shared_from_this();
    ret = playerEngine_->SetObs(obs);

    lastOpStatus_ = PLAYER_INITIALIZED;
    ChangeState(initializedState_);

    return MSERR_OK;
}

这样我们就跟踪到了PlayerServer的OnInfo()方法。

void PlayerServer::OnInfo(PlayerOnInfoType type, int32_t extra, const Format &infoBody)
{
    std::lock_guard<std::mutex> lockCb(mutexCb_);

    int32_t ret = HandleMessage(type, extra, infoBody);
    if (playerCb_ != nullptr && ret == MSERR_OK) {
        playerCb_->OnInfo(type, extra, infoBody);
    }
}

2. Play分析

从PlayerServer开始跟踪,调用到PlayerServer的OnPlay()方法。

int32_t PlayerServer::Play()
{
    ......
    if (lastOpStatus_ == PLAYER_PREPARED || lastOpStatus_ == PLAYER_PLAYBACK_COMPLETE ||
        lastOpStatus_ == PLAYER_PAUSED) {
        return OnPlay();
    } else {
        return MSERR_INVALID_OPERATION;
    }
}

在OnPlay中会启动一个任务,在任务中获取当前的状态,然后调用当前状态的Play()方法。

int32_t PlayerServer::OnPlay()
{
    ......
    auto playingTask = std::make_shared<TaskHandler<void>>([this]() {
        auto currState = std::static_pointer_cast<BaseState>(GetCurrState());
        (void)currState->Play();
    });

    int ret = taskMgr_.LaunchTask(playingTask, PlayerServerTaskType::STATE_CHANGE);

    lastOpStatus_ = PLAYER_STARTED;
    return MSERR_OK;
}

前面调用了PrepareAsync,所以当前的状态是Prepared,调用到了PreparedState的Play()方法,这个方法还是按照之前Prepare的方式,调回到PlayerServer的HandlePlay()。

int32_t PlayerServer::PreparedState::Play()
{
    return server_.HandlePlay();
}

在PlayServer中通过播放引擎继续向下调用。

int32_t PlayerServer::HandlePlay()
{
    int32_t ret = playerEngine_->Play();
    CHECK_AND_RETURN_RET_LOG(ret == MSERR_OK, MSERR_INVALID_OPERATION, "Engine Play Failed!");

    return MSERR_OK;
}

在PlayerEngineGstImpl的Play()方法会继续调用playBinCtrler_的Play()方法。

int32_t PlayerEngineGstImpl::Play()
{
    ......
    playBinCtrler_->Play();
    return MSERR_OK;
}

PlayBinCtrlerBase的Play()方法根据当前的State,调用currSate->Play()。

int32_t PlayBinCtrlerBase::Play()
{
    ......
    auto currState =    std::static_pointer_cast<BaseState>(GetCurrState());
    int32_t ret = currState->Play();

    return MSERR_OK;
}

在PreparedState的Play()方法中改变了PlayBin的状态为playing。

    int32_t PlayBinCtrlerBase::PreparedState::Play()
{
    GstStateChangeReturn ret;
    return ChangePlayBinState(GST_STATE_PLAYING, ret);
}

ChangePlayBinState主要是调用了

gst_element_set_state(GST_ELEMENT_CAST(ctrler_.playbin_),GST_STATE_PLAYING),这个直接调用了gstreamer三方库的实现,调用完这个方法以后,gstreamer就开始进行播放了。

int32_t PlayBinCtrlerBase::BaseState::ChangePlayBinState(GstState targetState, GstStateChangeReturn &ret)
{
    ......
    ret = gst_element_set_state(GST_ELEMENT_CAST(ctrler_.playbin_), targetState);
    if (ret == GST_STATE_CHANGE_FAILURE) {
        MEDIA_LOGE("Failed to change playbin's state to %{public}s", gst_element_state_get_name(targetState));
        return MSERR_INVALID_OPERATION;
    }

    return MSERR_OK;
}

六、总结

本篇文章主要从PlayerServer播放服务开始分析音视频播放的流程,涉及到gstreamer引擎的调用,相对于多媒体播放框架来说,更加底层,便于熟悉从框架到gstreamer的整体流程。

OpenHarmony实战开发-如何使用AVPlayer开发音频播放功能(ArkTS) 使用AVPlayer可以实现端到端播放原始媒体资源,本开发指导将以完整地播放一首音乐作为示例,向开发者讲解AVPlayer音频播放相关功能。 阅读详情

相关推荐

OpenHarmony 3.2 Beta多媒体系列——音视频播放框架

媒体子系统为开发者提供一套接口,方便开发者使用系统的媒体资源,主要包含音视频开发、相机开发、流媒体开发等模块。每个模块都提供给上层应用对应的接口,本文会对音视频开发中的音视频播放框架做一个详细的介绍。本文主要对OpenHarmony 3.2 Beta多媒体子系统的媒体播放进行介绍,首先梳理了整体的播放流程,然后对播放的主要步骤进行了详细地分析。媒体播放主要分为以下几个层次:(1) 提供给应用调用的Native接口,这个实际上通过。

maniuT的博客 1041

Gstreamer的音视频同步

一 概述     Gstreamer的音频视频同步,概括起来是一个比较大的问题,因为在网上可以看到很多音视频同步的办法。这里我们只看最普通的一种。以音频时钟做为参考时钟(要求参考时钟上的时间是线性递增的);生成数据流时依据参考时钟上的时间给每个数据块都打上时间戳(一般包括开始时间和结束时间);在播放时,读取数据上的时间戳,同时参考当前参考时钟上的时间来安排播放(如果数据块上的时间大于参考时钟

maeom的专栏 7125

星座SAR动目标检测(GMTI)

参考代码 星座SAR动目标检测 www.youwenfan.com/contentcsg/52399.html。

feifeigo123的博客 549

通俗的解释下音视频同步里pcr作用

PCR同步在非硬件精确时钟源的情况还是谨慎使用,gstreamer里面采用PCR同步,但是发现好多ffmpeg转的片儿,或者是CP方的片源,pcr打得很粗糙的,老是有跳帧等现象。 音视频同步,有三种方法,视频同步音频,音频同步视频,同步到外部时钟! 第三种,同步到外部时钟也就是PCR同步,和...

chenzhuangyong5728的博客 744

本地部署Llama 3中文版:Ollama量化配置与调优指南

大模型本地部署正在成为数据敏感场景下的主流选择。本地运行开源大模型,既能保护隐私又能降低调用成本,但中文能力往往成为瓶颈。以Llama 3为代表的开源模型,需借助中文微调与量化技术,才能在消费级显卡上流畅运行。量化通过压缩模型权重减小显存占用,配合Ollama等推理框架,可快速搭建私有中文AI服务。该方案适用于知识库问答、内容生成、离线助手等场景,并可通过OpenAI兼容接口无缝对接Dify等应用平台。本文从硬件选型、量化等级到Modelfile配置,系统梳理本地部署中文大模型的全链路实战方法与排错思路,帮

weixin_29035405的博客 82

从硅到氮化镓:用Sentaurus TCAD仿真GaN HEMT功率器件,我踩过的那些坑

本文分享了使用Sentaurus TCAD仿真GaN HEMT功率器件的实战经验,详细解析了从硅基到氮化镓的技术转型中遇到的五大关键挑战:材料参数设置、极化效应建模、网格划分策略、陷阱模型校准以及仿真与实测数据匹配。通过具体案例和参数优化方法,帮助工程师避开常见陷阱,提升TCAD仿真精度与效率。

weixin_28726671的博客 379

OpenHarmony AVCodec模块分析(六):速看5.0-Beta1更新了什么

本文将介绍OpenHarmony-v5.0-beta1 AVCodec模块更新的内容及应用的调用流程。

m0_64778763的博客 1791

PLC编程进阶:结构体(UDT)与功能块(FB)的模块化实践指南

本文深入探讨了PLC编程中实现模块化的核心方法,重点解析了结构体(UDT)与功能块(FB)的协同应用。通过将零散数据封装为UDT,并将控制逻辑打包为可复用的FB,能够构建出结构清晰、易于维护和调试的PLC程序。文章结合拧紧枪、电机控制等实战案例,提供了从设计原则到分层架构的完整实践指南,旨在帮助工程师将混乱的‘面条式’代码改造为高效的‘正规军’。

s3t4u的博客 219

OpenHarmony 3.1 Beta版本关键特性解析——HiStreamer轻量级可定制的媒体管线框架

(以下内容来自开发者分享,不代表 OpenHarmony 项目群工作委员会观点) 陈国栋 数字多媒体技术在过去的数十年里得到了飞速的发展,多媒体终端设备如智能音箱、智能门锁、智能手表广泛应用于人们生活中。如此小的设备却能承载如此多功能是如何实现的呢?HiStreamer 轻量级可定制的媒体管线框架为你解答! 接下来,咱们一起探秘 HiStreamer 轻量级可定制的媒体管线框架。 一、媒体管线框架是什么? 在讲 HiStreamer 之前,咱们先来了解下媒体管线框架是什么?.

OpenHarmony_dev的博客 1548

STM32H743 ADC多通道采样实战:CubeMX配置+DMA传输避坑指南

本文详细介绍了STM32H743 ADC多通道采样的实战配置,包括CubeMX参数设置、DMA传输优化及常见问题解决方案。重点解析了ADC模块的核心特性、多通道采样序列配置、Cache一致性处理等关键技术,帮助开发者高效实现稳定可靠的模拟信号采集系统。

weixin_29175469的博客 611

深入解析反射式DLL注入:从原理到实战优化

本文深入解析反射式DLL注入技术,从原理到实战优化全面剖析。通过PE文件内存重构、反射加载器实现及免杀增强方案,展示如何绕过安全检测并提升性能。适用于红队行动、内网渗透等场景,是高级攻防对抗中的关键技术。

weixin_28339967的博客 404

OpenHarmony4.0源码解析之媒体框架

从框架图中可以看出,媒体框架的主要工作模式为通过 Gstreamer 的插件自动化注册及插件组合功能,将其余媒体播放相关的框架功能插件化,配合 Gstreamer 自身丰富的插件,共同来对外提供音视频的录制与播放功能。如通过 audio-sink 及 audio-source 插件调用音频框架的播放及采集功能来实现音频的播放与录制;通过 surface-sink 调用图形框架,video-decoder 调用解码驱动模块实现视频的硬解播放等。

maniuT的博客 1565

后仿真中的必懂VCS仿真选项之initreg和deposit

后仿真中,我们常常对于没有复位寄存器和存储器单元,使用仿真选项之initreg和deposit,两种处理方式。今天做总结。请注意:请勿将两者进行同时使用。对于到底选取哪种方式,要结合实际的情况决断。但是,两者同时使用,会有冲突,得不到预期结果。

那么菜的博客 4330

Gstreamer的音视频同步 .

一 概述     Gstreamer的音频视频同步,概括起来是一个比较大的问题,因为在网上可以看到很多音视频同步的办法。这里我们只看最普通的一种。以音频时钟做为参考时钟(要求参考时钟上的时间是线性递增的);生成数据流时依据参考时钟上的时间给每个数据块都打上时间戳(一般包括开始时间和结束时间);在播放时,读取数据上的时间戳,同时参考当前参考时钟上的时间来安排播放(如果数据块上的时间大于参考时钟

勤奋努力 一飞冲天 3281

混合精度训练与推理:平衡性能与效率

在深度学习领域,模型的训练和推理通常需要大量的计算资源和时间。随着模型规模的不断增大,如大型语言模型、复杂的视觉模型等,对计算资源的需求也呈指数级增长。混合精度训练与推理技术应运而生,它通过在训练和推理过程中同时使用不同精度的数据类型,在保证模型性能的前提下,显著提高计算效率,减少内存占用。本文的目的是全面介绍混合精度训练与推理技术,包括其原理、实现方法、应用场景以及相关工具和资源,帮助读者深入理解并掌握该技术,从而在实际项目中更好地平衡性能与效率。

操作系统内核探秘的博客 1068

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

OpenHarmony多媒体播放器框架详解

首先根据RunCase可以大致了解一下播放音视频的主要流程,创建播放器,设置播放源,设置回调方法(包含播放过程中的多种状态的回调),设置播放显示的Surface,这些准备工作做好之后,需要调用播放器的PrepareASync方法,这个方法完成后,播放状态会变成Prepared状态,这时就可以调用播放器的play接口,进行音视频的播放了。RegisterTable()方法中,将字符串和对应的方法映射到Map中,这样后续的DoNext会根据输入的命令,来决定播放器具体的操作。这个方法中发起了一个播放的任务。

程序猿老樊的博客 1524

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Openharmony 媒体组件系统参数全梳理

OpenHarmony 媒体组件系统属性全梳理

m0_64778763的博客 1203

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

IDEA的使用指南

IDEA基于java的使用手册,可以最快速的上手,在使用中不断的丰富自己的使用技巧.

OpenHarmony开源鸿蒙学习入门 - 基于3.2Release 应用开发环境安装

OpenHarmony开源鸿蒙学习入门 - 3.2Release版本的IDE安装和环境配置。

路漫漫亦无涯 4003

音视频帧关于PCR同步问题

PCR同步在非硬件精确时钟源的情况还是谨慎使用,gstreamer里面采用PCR同步,但是发现好多ffmpeg转的片儿,或者是CP方的片源,pcr打得很粗糙的,老是有跳帧等现象。 音视频同步,有三种方法,视频同步音频,音频同步视频,同步到外部时钟! 第三种,同步到外部时钟也就是PCR同步,和我上面说的那种同步方式,一样! 用的最多的还是,视频同步音频,为什么呢?音频的采样率是固定的,若音频稍...

leek5533的博客 988

Gstreamer的一些基本概念与A/V同步分析

http://blog.csdn.net/shenbin1430/article/details/4291963 ================================= 一、媒体流(streams) 流线程中包含事件和缓存如下:-events-NEW_SEGMENT (NS)-EOS (EOS)*-TAG (T)-buffers (B) *...

whotodo 334

OpenHarmony之媒体组件模块简介

services目录下的内容可以粗略的理解为多媒体的系统服务,对应为OpenHarmony中的media_service进程,其下的engine目录中的内容是其核心所在,而services目录下主要是与frameworks/native下同名的接口,用于实现C/S模式。因为流水线上的每个环节都预先设置了其可以支持的数据类型与参数,在很多情况下,pipeline的结构也是根据数据的内容自动组建的(具体的协商过程不在本文讨论范围内)。从结构图中可以看出,提供给应用的接口层在基本能力的基础上提供了相当多的功能。

1 806
上一篇: OpenHarmony 3.2 Beta多媒体系列——音视频播放框架
下一篇: LiteOS-A内核中的procfs文件系统分析
OpenHarmony开发者
博客等级 码龄5年 626粉丝 221原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值