MEDIA ARCHITECTURE / URL & MSE

src 与 MSE:两种输入,两套集成

Chrome 公共管线与本地 tizen 分支对照:URL 交给原生播放器,MSE 在 Chromium 解析后通过 Mojo 向平台提供样本。

src:网页提供媒体 URL,播放器负责获取与解析。

MSE:网页获取媒体分片,SourceBuffer 接收字节,demuxer 解析后向下游提供压缩样本。

src / URL

Chrome · Chromium 公共管线

控制:WebMediaPlayerImpl · PipelineController

  1. 加载 URL 字节MultiBufferDataSource
  2. 浏览器侧解析容器FFmpegDemuxer
  3. 解码 → 视频帧 → 合成Renderer / Decoder → VideoFrame

URL 数据进入 Chromium 的解析与解码组件。

src / URL

Chromium‑Tizen · 平台 URL 播放器

控制:WebMediaPlayerImpl ⇄ Mojo ⇄ TizenRendererImpl

  1. 把媒体 URL 交给平台桥接层MediaUrlDemuxer / MediaPlayerRegistry
  2. 创建原生 URL 播放器MediaPlayerBridgeCapiTV
  3. 平台负责解析、解码与输出player_set_uri() · player_start()

主要交付 URL,由平台播放器管理媒体处理。

MSE / SourceBuffer

Chrome · Chromium 公共管线

控制:WebMediaPlayerImpl · PipelineController

  1. 网页获取分片并追加字节MediaSource / SourceBuffer.appendBuffer()
  2. 浏览器侧解析分片WebSourceBufferImpl → ChunkDemuxer
  3. 解码 → 视频帧 → 合成Renderer / Decoder → VideoFrame

应用控制分片供给,后续处理仍由 Chromium 管线承接。

MSE / SourceBuffer

Chromium‑Tizen · 平台 ES 播放器

控制:WebMediaPlayerImpl ⇄ Mojo ⇄ TizenRendererImpl

  1. 追加字节,仍在 Chromium 中解析WebSourceBufferImpl / ChunkDemuxer
  2. 压缩样本跨进程交接MojoDemuxerStreamImpl ⇄ Adapter
  3. 平台 ES 播放器解码与输出MediaPlayerESPlusPlayerTV

交付解析后的压缩样本,而不是直接把 URL 交给平台。

框图按职责与数据交接简化;控制 IPC 单独列示。Chrome 展示普通 URL / MSE 的公共实现,Decoder 由平台选择;Tizen 展示本地 tizen 分支的固定快照,其他版本与构建可不同。平台 API 以下的实现未展开。

ENCRYPTED MEDIA EXTENSIONS

EME:让网页与 CDM 协作播放加密内容

W3C 规范 ↗

网页负责能力选择与许可证消息交换;CDM 处理许可证和解密能力。EME 是接口规范,具体内容保护由所选 Key System 实现。

W3C EME 架构总览:网页应用通过 EME 与 CDM 交互,交换许可证消息;加密媒体沿浏览器媒体路径解密并播放。
W3C EME 原图 · 原始图片 ↗ · 点击图片放大
Copyright © 2026 World Wide Web Consortium · 文档许可 · 来源规范:Working Draft,2026-10-13
  1. 01

    选择能力,绑定 MediaKeys

    探测 Key System 与编码支持,创建 MediaKeys 并关联 video。

    requestMediaKeySystemAccess() → createMediaKeys() → setMediaKeys()
  2. 02

    用初始化数据生成请求

    从 encrypted 事件取得 initData,创建 session,让 CDM 生成许可证请求。

    createSession() → generateRequest()
  3. 03

    网页转发许可证消息

    处理 session 的 message 事件,将请求交给许可证服务器,再把响应交回 CDM。

    message → 许可证服务 → session.update()
  4. 04

    密钥可用,继续媒体处理

    观察 keystatuseschange;所需密钥可用后,媒体路径可继续解密、解码与呈现。

    keyStatuses → CDM / 平台安全媒体路径
许可证控制流

网页 ⇄ EME / CDM
网页 ⇄ 许可证服务器

媒体数据流

加密分片 → demux / 压缩样本 → 解密 → 解码 → 呈现

初始化数据、KID 与许可证消息不等于内容密钥。网页转发消息,不直接获得商业 DRM 明文密钥;具体解密可以由 CDM 或平台安全路径完成。

本地 tizen / Chromium 130.0.6723.116分支提交:f5439772622d · 2026-10-14

固定到已核对的本地分支提交,不宣称已同步远端。Chrome 参考图使用独立源码快照。

Tizen 源码调用链

从函数入口展开。箭头上的标签区分调用、任务、Mojo IPC 与回调;初始化与数据读取分阶段展示。

共用初始化:WebMediaPlayer → browser 平台 renderer

URL 与 MSE 共用控制桥接;资源分别以 URL 参数或 DemuxerStream endpoints 交接。

  1. 入口WebMediaPlayerImpl::Load() → DoLoad()

    加载入口;后续创建 demuxer 和启动 pipeline。

    third_party/blink/renderer/platform/media/web_media_player_impl.cc:739
    737: }
    738: 
    739: WebMediaPlayer::LoadTiming WebMediaPlayerImpl::Load(
    740:     LoadType load_type,
    741:     const WebMediaPlayerSource& source,
    742:     CorsMode cors_mode,
    743:     bool is_cache_disabled) {
    744:   // Only URL or MSE blob URL is supported.
    745:   DCHECK(source.IsURL());
    746:   WebURL url = source.GetAsURL();
    747:   DVLOG(1) << __func__ << "(" << load_type << ", " << GURL(url) << ", "
  2. 异步生命周期WebMediaPlayerImpl::StartPipeline()

    调用 DemuxerManager::CreateDemuxer();PipelineController 管理状态。

    third_party/blink/renderer/platform/media/web_media_player_impl.cc:3823
    3821: }
    3822: 
    3823: void WebMediaPlayerImpl::StartPipeline() {
    3824:   DCHECK(main_task_runner_->BelongsToCurrentThread());
    3825: 
    3826:   vfc_task_runner_->PostTask(
    3827:       FROM_HERE,
    3828:       base::BindOnce(&VideoFrameCompositor::SetOnNewProcessedFrameCallback,
    3829:                      base::Unretained(compositor_.get()),
    3830:                      base::BindPostTaskToCurrentDefault(base::BindOnce(
    3831:                          &WebMediaPlayerImpl::OnFirstFrame, weak_this_))));
  3. 工厂选择MediaPlayerRendererClientFactory::CreateRenderer()

    建立 Mojo renderer 及双向扩展接口;由 MediaFactory 选择此工厂。

    tizen_src/chromium_impl/content/renderer/media/tizen/media_player_renderer_client_factory.cc:23
    21: 
    22: std::unique_ptr<media::Renderer>
    23: MediaPlayerRendererClientFactory::CreateRenderer(
    24:     const scoped_refptr<base::SequencedTaskRunner>& media_task_runner,
    25:     const scoped_refptr<base::TaskRunner>& worker_task_runner,
    26:     media::AudioRendererSink* audio_renderer_sink,
    27:     media::VideoRendererSink* video_renderer_sink,
    28:     media::RequestOverlayInfoCB request_overlay_info_cb,
    29:     const gfx::ColorSpace& target_color_space) {
    30:   // Used to send messages from the MPRC (Renderer process), to the MPR (Browser
    31:   // process). The |renderer_extension_request| will be bound in
  4. Mojo IPC / 创建MediaInterfaceProxy::CreateMediaPlayerRenderer()

    browser 创建 TizenRendererImpl,交给 MojoRendererService 托管。

    content/browser/media/media_interface_proxy.cc:435
    433:   media::MojoRendererService::Create(
    434:       nullptr,
    435:       std::make_unique<content::TizenRendererImpl>(
    436:           render_frame_host().GetProcess()->GetID(),
    437:           render_frame_host().GetRoutingID(),
    438:           WebContents::FromRenderFrameHost(&render_frame_host()),
    439:           std::move(renderer_extension_receiver),
    440:           std::move(client_extension_remote)),
    441:       std::move(receiver));
    442: }
    443: #endif
  5. 初始化阶段MediaPlayerRendererClient::Initialize() → MojoRendererWrapper::Initialize()

    renderer 侧包装层将初始化转给 MojoRenderer。

    tizen_src/chromium_impl/content/renderer/media/tizen/media_player_renderer_client.cc:46
    44: }
    45: 
    46: void MediaPlayerRendererClient::Initialize(
    47:     media::MediaResource* media_resource,
    48:     media::RendererClient* client,
    49:     media::PipelineStatusCallback init_cb) {
    50:   DCHECK(media_task_runner_->RunsTasksInCurrentSequence());
    51:   DCHECK(!init_cb_);
    52: 
    53:   LOG(INFO) << "MediaPlayerRendererClient Initialize:" << (void*)this;
    54: 
  6. 资源类型分支MojoRenderer::InitializeRendererFromUrl() / InitializeRendererFromStreams()

    URL 发送 MediaUrlParams;stream 绑定 MojoDemuxerStreamImpl 并发送 endpoints。

    media/mojo/clients/mojo_renderer.cc:63
    61:   init_cb_ = std::move(init_cb);
    62: 
    63:   switch (media_resource_->GetType()) {
    64:     case MediaResource::Type::kStream:
    65:       InitializeRendererFromStreams(client);
    66:       break;
    67:     case MediaResource::Type::KUrl:
    68:       InitializeRendererFromUrl(client);
    69:       break;
    70:   }
    71: }
  7. Mojo IPC / 异步就绪MojoRendererService::Initialize() / OnAllStreamsReady()

    URL 重建 MediaUrlDemuxer;stream 建立 MediaResourceShim,流就绪后调用 renderer。

    media/mojo/services/mojo_renderer_service.cc:56
    54: MojoRendererService::~MojoRendererService() = default;
    55: 
    56: void MojoRendererService::Initialize(
    57:     mojo::PendingAssociatedRemote<mojom::RendererClient> client,
    58:     std::optional<std::vector<mojo::PendingRemote<mojom::DemuxerStream>>>
    59:         streams,
    60:     mojom::MediaUrlParamsPtr media_url_params,
    61:     InitializeCallback callback) {
    62:   LOG(INFO) << "MojoRendererService Initialize:" << (void*)this;
    63:   DCHECK_EQ(state_, STATE_UNINITIALIZED);
    64: 
  8. browser 平台桥接TizenRendererImpl::Initialize() → MediaPlayerRegistry::CreateMediaPlayer()

    按资源类型选择平台工厂,创建并初始化原生播放器。

    tizen_src/chromium_impl/content/browser/media/tizen_renderer_impl.cc:202
    200: 
    201:   // Create a media player and get player id for the created media player.
    202:   media_player_ = media::MediaPlayerRegistry::GetInstance()->CreateMediaPlayer(
    203:       media_resource_, user_agent, client_, volume_, player_id_);
    204: 
    205: #if BUILDFLAG(IS_TIZEN_TV)
    206:   client_extension_->UpdateVideoId(player_id_);
    207:   media_player_->SetContentMimeType(mime_type_);
    208: #endif
    209: 
    210:   if (!media_player_->CreatePlayer(player_id_)) {
src / URL:把 URL 交给原生播放器

展示 Tizen TV 的普通平台 URL 路径;音频、upstream 模式和 HbbTV 等条件分支另列。

  1. URL 路径选择ContentRendererClient::OverrideDemuxerForUrl()

    TV / 非 upstream 模式下可用 MediaUrlDemuxer;MSE 或音频条件直接返回 nullptr。

    content/public/renderer/content_renderer_client.cc:188
    186: #endif
    187: 
    188: std::unique_ptr<media::Demuxer> ContentRendererClient::OverrideDemuxerForUrl(
    189:     RenderFrame* render_frame,
    190:     const GURL& url,
    191: #if BUILDFLAG(IS_TIZEN_TV)
    192:     bool is_media_source,
    193: #endif
    194:     scoped_refptr<base::SequencedTaskRunner> task_runner) {
    195: #if defined(TIZEN_MULTIMEDIA) && BUILDFLAG(IS_TIZEN_TV)
    196:   if (media::IsUpstreamArchitectureEnabled()) {
  2. 资源创建DemuxerManager::CreateDemuxer()

    管理 URL demuxer / override;这里的 MediaUrlDemuxer 是 URL 资源桥,不负责拆出平台媒体包。

    media/filters/demuxer_manager.cc:393
    391: }
    392: 
    393: PipelineStatus DemuxerManager::CreateDemuxer(
    394:     bool load_media_source,
    395: #if defined(SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE)
    396:     bool load_elementary_media_stream_source,
    397: #endif  // defined(SAMSUNG_ELEMENTARY_MEDIA_STREAM_SOURCE)
    398:     DataSource::Preload preload,
    399:     bool needs_first_frame,
    400:     DemuxerManager::DemuxerCreatedCB on_demuxer_created,
    401:     base::flat_map<std::string, std::string> headers) {
  3. Mojo IPC / URL 参数MojoRenderer::InitializeRendererFromUrl()

    将媒体 URL 与相关请求参数发送到 browser;共用初始化链见上一节。

    media/mojo/clients/mojo_renderer.cc:109
    107: }
    108: 
    109: void MojoRenderer::InitializeRendererFromUrl(media::RendererClient* client) {
    110:   DVLOG(2) << __func__;
    111:   DCHECK(task_runner_->RunsTasksInCurrentSequence());
    112: 
    113:   BindRemoteRendererIfNeeded();
    114: 
    115:   const MediaUrlParams& url_params = media_resource_->GetMediaUrlParams();
    116: 
    117:   // Using base::Unretained(this) is safe because |this| owns
  4. browser 工厂分支MediaPlayerRegistry::CreateMediaPlayer()

    URL 默认选择 BridgeCapi 工厂;启用 PlusPlayer 的 HbbTV 构建存在独立分支。

    tizen_src/chromium_impl/media/filters/media_player_registry.cc:63
    61: }
    62: 
    63: std::unique_ptr<MediaPlayerTizen> MediaPlayerRegistry::CreateMediaPlayer(
    64:     MediaResource* media_resource,
    65:     const std::string& user_agent,
    66:     media::RendererClient* client,
    67:     double volume,
    68:     int& player_id) {
    69:   LOG(INFO) << __func__;
    70:   std::unique_ptr<MediaPlayerFactory> factory;
    71:   if (media_resource->GetType() == MediaResource::Type::kStream) {
  5. TV 编译分支MediaPlayerBridgeCapiFactory::Create()

    IS_TIZEN_TV 创建 MediaPlayerBridgeCapiTV,其他构建使用 common 类。

    tizen_src/chromium_impl/media/filters/media_player_bridge_capi_factory.cc:28
    26:             << media_resource->GetMediaUrlParams().media_url.spec()
    27:             << ", clean url: " << clean_url.spec();
    28:   return std::make_unique<MediaPlayerBridgeCapiTV>(clean_url, user_agent,
    29:                                                    volume);
    30: #else
    31:   return std::make_unique<MediaPlayerBridgeCapi>(
    32:       media_resource->GetMediaUrlParams().media_url, user_agent, volume);
    33: #endif
    34: }
    35: 
    36: }  // namespace media
  6. 平台 API / 配置MediaPlayerBridgeCapi::Prepare() → player_set_uri()

    普通 TV URL 在 Prepare 中交给平台;HbbTV 可使用 TV 子类的 URL 配置分支。

    tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc:242
    240:       LOG(INFO) << "Blob URL detected, using temp file: " << url_str;
    241:     }
    242:     player_set_uri(player_, url_str.c_str());
    243:   }
    244: #else
    245:   player_set_uri(player_, url_.spec().c_str());
    246: #endif
    247: 
    248: #if !defined(EWK_BRINGUP)
    249:   player_set_sound_type(player_, SOUND_TYPE_MEDIA);
    250: #endif
  7. 平台异步准备Prepare() → player_prepare_async()

    准备过程异步完成;不是配置 URI 后立即同步播放。

    tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc:348
    346:   LOG_ID(INFO, player_id_) << "Call player_prepare_async";
    347: 
    348:   int ret = player_prepare_async(player_, PlayerPreparedCb, this);
    349:   if (ret != PLAYER_ERROR_NONE) {
    350:     is_preparing_ = false;
    351:     OnHandlePlayerError(ret, FROM_HERE);
    352:     return false;
    353:   }
    354: 
    355:   is_preparing_ = true;
    356:   return true;
  8. 平台 API / 播放MediaPlayerBridgeCapi::Play() → player_start()

    在准备、状态与播放请求满足条件时启动平台播放。

    tizen_src/chromium_impl/media/filters/media_player_bridge_capi.cc:488
    486: }
    487: 
    488: bool MediaPlayerBridgeCapi::Play() {
    489:   LOG_ID(INFO, player_id_) << "(" << static_cast<void*>(this) << ") "
    490:                            << __func__;
    491: 
    492:   if (IsPlayerSuspended()) {
    493:     LOG(INFO) << "(" << static_cast<void*>(this) << ") " << __func__
    494:               << " Player is suspended...";
    495:     is_play_pending_ = true;
    496:     delayed_player_state_ = PLAYER_STATE_DELAYED_PLAY;
MSE / MP4:从 appendBuffer 到 HEVC 样本解析

此链在 renderer 处理分片并形成压缩样本。HEVC 是 codec 条件分支,不代表所有 MSE 内容都经过 HEVC。

  1. JS / BlinkSourceBuffer.appendBuffer() → AppendBufferInternal()

    检查追加状态并接收 JS 提供的媒体字节。

    third_party/blink/renderer/modules/mediasource/source_buffer.cc:602
    600: }
    601: 
    602: void SourceBuffer::appendBuffer(DOMArrayBuffer* data,
    603:                                 ExceptionState& exception_state) {
    604:   DVLOG(2) << __func__ << " this=" << this << " size=" << data->ByteLength();
    605:   // Section 3.2 appendBuffer()
    606:   // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#widl-SourceBuffer-appendBuffer-void-ArrayBufferView-data
    607:   AppendBufferInternal(data->ByteSpan(), exception_state);
    608: }
    609: 
    610: void SourceBuffer::appendBuffer(NotShared<DOMArrayBufferView> data,
  2. 字节缓存WebSourceBufferImpl::AppendToParseBuffer()

    先把追加字节交给 ChunkDemuxer 的解析缓冲。

    third_party/blink/renderer/platform/media/web_source_buffer_impl.cc:137
    135: }
    136: 
    137: bool WebSourceBufferImpl::AppendToParseBuffer(
    138:     base::span<const unsigned char> data) {
    139:   return demuxer_->AppendToParseBuffer(id_, data);
    140: }
    141: 
    142: media::StreamParser::ParseStatus WebSourceBufferImpl::RunSegmentParserLoop(
    143:     double* timestamp_offset) {
    144:   base::TimeDelta old_offset = timestamp_offset_;
    145:   media::StreamParser::ParseStatus parse_result =
  3. 任务边界 / 异步SourceBuffer::AppendBufferAsyncPart_Locked()

    排队任务运行 segment parser loop;可能分多轮处理。

    third_party/blink/renderer/modules/mediasource/source_buffer.cc:2130
    2128: }
    2129: 
    2130: void SourceBuffer::AppendBufferAsyncPart_Locked(
    2131:     MediaSourceAttachmentSupplement::ExclusiveKey pass_key) {
    2132:   DCHECK(source_);
    2133:   source_->AssertAttachmentsMutexHeldIfCrossThreadForDebugging();
    2134:   DCHECK(updating_);
    2135: 
    2136:   // Section 3.5.4 Buffer Append Algorithm
    2137:   // https://dvcs.w3.org/hg/html-media/raw-file/default/media-source/media-source.html#sourcebuffer-buffer-append
    2138: 
  4. 分片解析WebSourceBufferImpl::RunSegmentParserLoop() → ChunkDemuxer::RunSegmentParserLoop()

    把 append window 与 timestamp offset 交给 demuxer。

    third_party/blink/renderer/platform/media/web_source_buffer_impl.cc:142
    140: }
    141: 
    142: media::StreamParser::ParseStatus WebSourceBufferImpl::RunSegmentParserLoop(
    143:     double* timestamp_offset) {
    144:   base::TimeDelta old_offset = timestamp_offset_;
    145:   media::StreamParser::ParseStatus parse_result =
    146:       demuxer_->RunSegmentParserLoop(id_, append_window_start_,
    147:                                      append_window_end_, &timestamp_offset_);
    148: 
    149:   // Coded frame processing may update the timestamp offset. If the caller
    150:   // provides a non-nullptr |timestamp_offset| and frame processing changes the
  5. 容器解析 / MP4 条件SourceBufferState::RunSegmentParserLoop() → MP4StreamParser::Parse()

    MP4 parser 分析 box、track run 与媒体样本。

    media/filters/source_buffer_state.cc:234
    232:   // and timestamp offset pointer. See http://crbug.com/351454.
    233:   StreamParser::ParseStatus result =
    234:       stream_parser_->Parse(StreamParser::kMaxPendingBytesPerParse);
    235: 
    236:   if (result == StreamParser::ParseStatus::kFailed) {
    237:     MEDIA_LOG(ERROR, media_log_)
    238:         << __func__ << ": stream parsing failed. append_window_start="
    239:         << append_window_start.InSecondsF()
    240:         << " append_window_end=" << append_window_end.InSecondsF();
    241:   }
    242: 
  6. 样本准备MP4StreamParser::EnqueueSample() → TrackRunIterator::GetDecryptConfig()

    加密样本取得 KID / IV / subsamples;样本输出阶段附加时间戳与配置。

    media/formats/mp4/mp4_stream_parser.cc:1248
    1246:   std::vector<SubsampleEntry> subsamples;
    1247:   if (runs_->is_encrypted()) {
    1248:     decrypt_config = runs_->GetDecryptConfig();
    1249:     if (!decrypt_config)
    1250:       return ParseResult::kError;
    1251:     subsamples = decrypt_config->subsamples();
    1252:   }
    1253: 
    1254:   // This may change if analysis results indicate runs_->is_keyframe() is
    1255:   // opposite of what the coded frame contains.
    1256:   bool is_keyframe = runs_->is_keyframe();
  7. HEVC 条件分支HEVCBitstreamConverter::ConvertAndAnalyzeFrame()

    长度前缀转 Annex B,分析关键帧并按需插入参数集;同步维护 subsamples。

    media/formats/mp4/hevc.cc:649
    647: }
    648: 
    649: bool HEVCBitstreamConverter::ConvertAndAnalyzeFrame(
    650:     std::vector<uint8_t>* frame_buf,
    651:     bool is_keyframe,
    652:     std::vector<SubsampleEntry>* subsamples,
    653:     AnalysisResult* analysis_result) const {
    654:   RCHECK(AVC::ConvertFrameToAnnexB(hevc_config_->lengthSizeMinusOne + 1,
    655:                                    frame_buf, subsamples));
    656:   // |is_keyframe| may be incorrect. Analyze the frame to see if it is a
    657:   // keyframe. |is_keyframe| will be used if the analysis is inconclusive.
  8. NAL 解析HEVC::AnalyzeAnnexB() / InsertParamSetsAnnexB() → H265NaluParser

    分析与参数集插入路径调用 NAL parser,属于解密前的码流结构处理。

    media/formats/mp4/hevc.cc:461
    459:   // units
    460:   while (true) {
    461:     H265NaluParser::Result h265_result = parser.AdvanceToNextNALU(&nalu);
    462:     if (h265_result == H265NaluParser::kEOStream) {
    463:       break;
    464:     }
    465: 
    466:     if (h265_result != H265NaluParser::kOk) {
    467:       DCHECK_NE(h265_result, H265NaluParser::kUnsupportedStream)
    468:           << "AdvanceToNextNALU() returned kUnsupportedStream!";
    469:       return result;
  9. codec parserH265NaluParser::AdvanceToNextNALU()

    读取 NAL 头;此快照中的具体检查以代码片段为准,不套用 M94 或历史故障提交。

    media/parsers/h265_nalu_parser.cc:128
    126: }
    127: 
    128: H265NaluParser::Result H265NaluParser::AdvanceToNextNALU(H265NALU* nalu) {
    129:   off_t start_code_size;
    130:   off_t nalu_size_with_start_code;
    131:   if (!LocateNALU(&nalu_size_with_start_code, &start_code_size)) {
    132:     DVLOG(4) << "Could not find next NALU, bytes left in stream: "
    133:              << bytes_left_;
    134:     return kEOStream;
    135:   }
    136: 
  10. 缓冲交接MP4StreamParser → StreamParserBuffer / buffers

    形成带压缩样本和元数据的输出,交给后续缓冲及异步读取;append 不直接调用平台 submit。

    media/formats/mp4/mp4_stream_parser.cc:1410
    1408: 
    1409:   if (decrypt_config)
    1410:     stream_buf->set_decrypt_config(std::move(decrypt_config));
    1411: 
    1412:   if (runs_->duration() != kNoTimestamp) {
    1413:     stream_buf->set_duration(runs_->duration());
    1414:   } else {
    1415:     MEDIA_LOG(ERROR, media_log_) << "Frame duration exceeds representable "
    1416:                                  << "limit";
    1417:     return ParseResult::kError;
    1418:   }
MSE / ES:平台请求样本 → Mojo 返回 → 喂入播放器

读取方向为 browser → renderer,数据回传方向相反;异步回调后才进入平台样本队列。

  1. 预先绑定 streamsMojoRenderer::InitializeRendererFromStreams()

    renderer 将本地 DemuxerStream 暴露为 Mojo 接口。

    media/mojo/clients/mojo_renderer.cc:85
    83:   for (media::DemuxerStream* stream : streams) {
    84:     mojo::PendingRemote<mojom::DemuxerStream> stream_proxy;
    85:     auto mojo_stream = std::make_unique<MojoDemuxerStreamImpl>(
    86:         stream, stream_proxy.InitWithNewPipeAndPassReceiver());
    87: 
    88:     // Using base::Unretained(this) is safe because |this| owns |mojo_stream|,
    89:     // and the error handler can't be invoked once |mojo_stream| is destroyed.
    90:     mojo_stream->set_disconnect_handler(
    91:         base::BindOnce(&MojoRenderer::OnDemuxerStreamConnectionError,
    92:                        base::Unretained(this), mojo_stream.get()));
    93: 
  2. browser 适配MediaResourceShim → MojoDemuxerStreamAdapter

    browser 把远程 streams 包装为本地可读的 DemuxerStream。

    media/mojo/services/media_resource_shim.cc:23
    21: 
    22:   for (auto& s : streams) {
    23:     streams_.emplace_back(std::make_unique<MojoDemuxerStreamAdapter>(
    24:         std::move(s), base::BindOnce(&MediaResourceShim::OnStreamReady,
    25:                                      weak_factory_.GetWeakPtr())));
    26:   }
    27: }
    28: 
    29: MediaResourceShim::~MediaResourceShim() = default;
    30: 
    31: std::vector<DemuxerStream*> MediaResourceShim::GetAllStreams() {
  3. 平台流播放器MediaPlayerRegistry → MediaPlayerESPlusPlayerFactory::Create()

    kStream 资源选择 ESPlusPlayer,TV 构建创建 TV 子类。

    tizen_src/chromium_impl/media/filters/media_player_esplusplayer_factory.cc:21
    19:     double volume) {
    20: #if BUILDFLAG(IS_TIZEN_TV)
    21:   return std::make_unique<MediaPlayerESPlusPlayerTV>();
    22: #else
    23:   return std::make_unique<MediaPlayerESPlusPlayerCommon>();
    24: #endif
    25: }
    26: 
    27: }  // namespace media
  4. browser 拉取MediaPlayerESPlusPlayer::ReadBuffer()

    先消耗已有队列;检查背压、暂停、EOS 和 pending read,再请求一批样本。

    tizen_src/chromium_impl/media/filters/media_player_esplusplayer.cc:998
    996: }
    997: 
    998: void MediaPlayerESPlusPlayer::ReadBuffer(DemuxerStream::Type type) {
    999:   if (!ReadFromBufferQueue(type))
    1000:     return;
    1001: 
    1002:   // Avoid unnecessary or redundant read requests.
    1003:   if (!ShouldFeed(type) || ReadRequested(type) || IsEos(type) ||
    1004:       (is_prepared_ && is_paused_ && !is_seeking_)) {
    1005:     return;
    1006:   }
  5. Mojo IPC / 请求MojoDemuxerStreamAdapter::Read() ⇢ MojoDemuxerStreamImpl::Read()

    读取请求通过 Mojo 发往 renderer 的 stream 实现。

    media/mojo/services/mojo_demuxer_stream_adapter.cc:43
    41: 
    42:   read_cb_ = std::move(read_cb);
    43:   demuxer_stream_->Read(count,
    44:                         base::BindOnce(&MojoDemuxerStreamAdapter::OnBufferReady,
    45:                                        weak_factory_.GetWeakPtr()));
    46: }
    47: 
    48: AudioDecoderConfig MojoDemuxerStreamAdapter::audio_decoder_config() {
    49:   DCHECK_EQ(type_, AUDIO);
    50:   return audio_config_;
    51: }
  6. renderer / 异步回调MojoDemuxerStreamImpl::Read() → stream_->Read()

    异步读取本地压缩样本,并在 OnBufferReady 中序列化返回。

    media/mojo/clients/mojo_demuxer_stream_impl.cc:55
    53: }
    54: 
    55: void MojoDemuxerStreamImpl::Read(uint32_t count, ReadCallback callback) {
    56:   DVLOG(3) << __func__ << ": count=" << count;
    57:   stream_->Read(
    58:       count, base::BindOnce(&MojoDemuxerStreamImpl::OnBufferReady,
    59:                             weak_factory_.GetWeakPtr(), std::move(callback)));
    60: }
    61: 
    62: void MojoDemuxerStreamImpl::EnableBitstreamConverter() {
    63:   stream_->EnableBitstreamConverter();
  7. Mojo 数据返回MojoDemuxerStreamAdapter::OnBufferReady() → OnBufferRead()

    通过 buffer reader 还原数据;回调完成后向平台读取者交付 buffer。

    media/mojo/services/mojo_demuxer_stream_adapter.cc:134
    132: 
    133:   batch_buffers_ = std::move(batch_buffers);
    134:   mojo_decoder_buffer_reader_->ReadDecoderBuffer(
    135:       std::move(batch_buffers_[0]),
    136:       base::BindOnce(&MojoDemuxerStreamAdapter::OnBufferRead,
    137:                      weak_factory_.GetWeakPtr()));
    138: }
    139: 
    140: void MojoDemuxerStreamAdapter::OnBufferRead(
    141:     scoped_refptr<DecoderBuffer> buffer) {
    142:   if (!buffer) {
  8. browser 回调MediaPlayerESPlusPlayer::OnBufferReady() → ReadBuffer()

    检查状态与 flush generation,将有效样本放入队列后继续喂入。

    tizen_src/chromium_impl/media/filters/media_player_esplusplayer.cc:1046
    1044: }
    1045: 
    1046: void MediaPlayerESPlusPlayer::OnBufferReady(
    1047:     uint64_t posted_flush_id,
    1048:     DemuxerStream::Type type,
    1049:     DemuxerStream::Status status,
    1050:     DemuxerStream::DecoderBufferVector buffers) {
    1051:   // Defensive check: drop buffers if player has been released.
    1052:   // After Release(), esplayer_ handle is closed and player state is cleaned up.
    1053:   // OnBufferReady may still be called by pending DemuxerStream::Read() callbacks.
    1054:   if (IsReleased()) {
  9. 背压 / 平台提交MediaPlayerESPlusPlayer::ReadFromBufferQueue() → SubmitEsPacket()

    提交队首样本;FULL / NOT_PREPARED 等状态保留队列并停止继续喂入。

    tizen_src/chromium_impl/media/filters/media_player_esplusplayer.cc:1118
    1116:     status = SubmitEosPacket(type);
    1117:   else
    1118:     status = SubmitEsPacket(type, buffer);
    1119: 
    1120:   if (status == ESPLUSPLAYER_SUBMIT_STATUS_FULL) {
    1121:     return false;
    1122:   }
    1123: 
    1124:   if ((status == ESPLUSPLAYER_SUBMIT_STATUS_NOT_PREPARED) ||
    1125:       (status == ESPLUSPLAYER_SUBMIT_STATUS_OUT_OF_MEMORY))
    1126:     return false;
  10. 平台 API 边界MediaPlayerESPlusPlayerTV::SubmitEsPacket()

    按 DRM / handle 分支提交 encrypted packet、trust-zone packet,或调用普通父类提交。

    tizen_src/chromium_impl/media/filters/media_player_esplusplayer_tv.cc:644
    642: }
    643: 
    644: esplusplayer_submit_status MediaPlayerESPlusPlayerTV::SubmitEsPacket(
    645:     DemuxerStream::Type type,
    646:     scoped_refptr<DecoderBuffer> buffer) {
    647:   // Defensive check: prevent submitting packets after Release() has been called.
    648:   // This guards against race conditions where OnBufferReady may still execute
    649:   // after the player has been released and esplayer_ handle has been closed.
    650:   if (IsReleased()) {
    651:     LOG_ID(WARNING, player_id_)
    652:         << "Player is released, skip submit "

分支条件:TIZEN_MULTIMEDIA 且未启用 upstream architecture 的平台集成;TV、音频、HbbTV / PlusPlayer、EMSS 可走不同分支。平台 API 以下不推断芯片实现。MSE 的样本解析与拉取是两条异步链路,不是一条同步栈。

完整函数定位、原始片段与文件哈希 ↗
源码与版本依据

Chromium 参考快照:8c7b7ff9514f38006379aa1ae3115eae9faae71c。普通 URL 图选取 FFmpeg 分支,不覆盖 HLS、override 等所有加载方式。

本地 tizen 快照:f5439772622daf5a2f513e4c305f690b9d49ba9d。chrome/VERSION:130.0.6723.116。这里只核对源码,不代表硬件运行 trace。

此版本已使用 WebMediaPlayerImpl / Mojo / TizenRendererImpl;旧 M94 的 WebMediaPlayerEfl、RendererDemuxerEfl / BrowserDemuxerEfl 不作为当前图依据。

版本与文件哈希 ↗ · Tizen 函数调用证据 ↗ · Chromium 参考源码记录 ↗