音视频学习(三十一):DASH协议

发布于:2025-03-31 ⋅ 阅读:(18) ⋅ 点赞:(0)

DASH(Dynamic Adaptive Streaming over HTTP,动态自适应流媒体传输)是一种基于 HTTP 的流媒体传输协议,类似于 HLS,但更通用,支持 MPEG-2 TSISO Base Media File Format(MP4) 等封装格式。它允许客户端根据网络状况动态切换码率,确保流畅播放。

特点

  • 基于 HTTP:使用普通 Web 服务器进行内容分发,无需专用流媒体服务器。
  • 自适应码率:客户端可根据带宽动态调整播放的码率,避免卡顿。
  • 多种封装格式:支持 MP4(fMP4)MPEG-2 TS 等容器格式。
  • CDN 友好:DASH 片段是普通的 HTTP 文件,易于缓存和分发。
  • 支持多种音视频编码:H.264、H.265、VP9、AV1 等。

工作流程

DASH 采用**分片(Segment)+ 清单文件(MPD,Media Presentation Description)**的方式传输流媒体内容。

基本组件

组件 作用
MPD(Media Presentation Description) 描述 DASH 播放列表,定义视频流的可用分辨率、码率、分片 URL。
Segment(媒体分片) 视频/音频数据被切割成多个小片段,每个片段几秒钟,客户端可选择合适的分片下载。
Representation(表示) 一个视频的不同码率/分辨率的版本,如 360p, 720p, 1080p。
AdaptationSet(适配集合) 一组 Representation,通常是不同的音视频流,如多种语言音轨、不同清晰度视频流。

DASH 播放过程

  1. 客户端请求 MPD 文件
    • 例如 https://example.com/video/manifest.mpd
  2. 解析 MPD,获取可用码率、分辨率、片段 URL
  3. 根据网络情况,选择合适的码率(Representation)
  4. 下载首个分片(Segment)并开始播放
  5. 动态调整码率
    • 如果网络带宽增加,切换到更高清的流;
    • 如果网络变差,降级到低码率流,保证不卡顿。
  6. 持续请求新片段
    • 例如 https://example.com/video/segment_1.m4s, segment_2.m4s,直到播放完毕。

MPD 文件结构

MPD 是 XML 格式的清单文件,定义视频流的不同清晰度和码率。

示例:

<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011" type="static" mediaPresentationDuration="PT10M">
    <Period>
        <AdaptationSet mimeType="video/mp4" segmentAlignment="true">
            <!-- 1080p 版本 -->
            <Representation id="1080p" bandwidth="5000000" width="1920" height="1080" codecs="avc1.640028">
                <BaseURL>1080p/</BaseURL>
                <SegmentTemplate timescale="1000" media="segment_$Number$.m4s" startNumber="1" />
            </Representation>
            <!-- 720p 版本 -->
            <Representation id="720p" bandwidth="2500000" width="1280" height="720" codecs="avc1.64001F">
                <BaseURL>720p/</BaseURL>
                <SegmentTemplate timescale="1000" media="segment_$Number$.m4s" startNumber="1" />
            </Representation>
        </AdaptationSet>
    </Period>
</MPD>

字段说明解释:

  • MPD:根元素,定义整个 DASH 播放内容。
  • Period:表示一个播放周期(多个 Period 可用于直播)。
  • AdaptationSet:定义一组可互换的流,如不同分辨率的视频。
  • Representation
    • id="1080p":该流的 ID,表示 1080p 版本。
    • bandwidth="5000000":码率 5Mbps。
    • codecs="avc1.640028":H.264 编码。
  • BaseURL:片段所在的路径。
  • SegmentTemplate
    • media="segment_$Number$.m4s":片段命名格式,如 segment_1.m4ssegment_2.m4s
    • startNumber="1":从 segment_1.m4s 开始播放。

DASH的模式

VOD(点播)

  • type="static"(静态 MPD),MPD 预先定义好所有片段,适用于点播视频
  • 片段是固定的,不会实时更新。

Live(直播)

  • type="dynamic"(动态 MPD),MPD 会不断更新,支持直播流。

  • 直播 MPD 示例:

    <MPD type="dynamic" minimumUpdatePeriod="PT5S">
        <Period>
            <AdaptationSet>
                <SegmentTemplate media="live_$Number$.m4s" startNumber="1000" />
            </AdaptationSet>
        </Period>
    </MPD>
    
    • minimumUpdatePeriod="PT5S":每 5 秒更新一次 MPD。
    • startNumber="1000":直播流的分片编号从 1000 开始。

DASH 与 HLS 对比

特性 DASH HLS
开发组织 MPEG Apple
封装格式 MP4(fMP4)、TS TS、fMP4
码率切换 客户端自适应 客户端自适应
加密 支持 Widevine、PlayReady、FairPlay 主要支持 FairPlay
CDN 兼容性 更适合 Apple 生态
播放器支持 支持 HTML5、ExoPlayer、Shaka Player Apple 设备原生支持
直播支持
  • DASH 适用于 PC、Android、Windows 设备,但 iOS 设备上不原生支持。
  • HLS 是 Apple 生态的标准,iPhone/iPad/Safari 直接支持。

DASH 播放器

常见的 DASH 播放器:

  • Shaka Player(Google):开源,支持 DRM(推荐)
  • dash.js(官方):MPEG-DASH 官方播放器
  • ExoPlayer(Android):支持 DASH、HLS
  • Video.js + Dash Plugin:支持 DASH 播放

示例:使用 dash.js 在 HTML5 中播放 DASH 视频:

<video id="video" controls></video>
<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>
<script>
    var player = dashjs.MediaPlayer().create();
    player.initialize(document.getElementById("video"), "https://example.com/video/manifest.mpd", true);
</script>

DASH应用(c++)

C++ 解析 DASH MPD 文件

MPD 文件是 XML 格式,可以使用 TinyXML2RapidXMLpugixml 解析它。

// c++ 使用 TinyXML2 解析MPD文件
#include <iostream>
#include <string>
#include <vector>
#include "tinyxml2.h"

using namespace tinyxml2;
using namespace std;

struct Representation {
    string id;
    int bandwidth;
    int width, height;
    string codecs;
    string baseURL;
    string segmentTemplate;
    int startNumber;
};

vector<Representation> parseMPD(const string& xmlFile) {
    XMLDocument doc;
    if (doc.LoadFile(xmlFile.c_str()) != XML_SUCCESS) {
        cerr << "Failed to load MPD file!" << endl;
        return {};
    }

    vector<Representation> representations;
    XMLElement* mpd = doc.FirstChildElement("MPD");
    if (!mpd) return representations;

    XMLElement* period = mpd->FirstChildElement("Period");
    if (!period) return representations;

    XMLElement* adaptationSet = period->FirstChildElement("AdaptationSet");
    while (adaptationSet) {
        XMLElement* representation = adaptationSet->FirstChildElement("Representation");
        while (representation) {
            Representation rep;
            rep.id = representation->Attribute("id");
            representation->QueryIntAttribute("bandwidth", &rep.bandwidth);
            representation->QueryIntAttribute("width", &rep.width);
            representation->QueryIntAttribute("height", &rep.height);
            rep.codecs = representation->Attribute("codecs");

            XMLElement* baseURL = representation->FirstChildElement("BaseURL");
            if (baseURL) rep.baseURL = baseURL->GetText();

            XMLElement* segmentTemplate = representation->FirstChildElement("SegmentTemplate");
            if (segmentTemplate) {
                rep.segmentTemplate = segmentTemplate->Attribute("media");
                segmentTemplate->QueryIntAttribute("startNumber", &rep.startNumber);
            }

            representations.push_back(rep);
            representation = representation->NextSiblingElement("Representation");
        }
        adaptationSet = adaptationSet->NextSiblingElement("AdaptationSet");
    }
    return representations;
}

int main() {
    string xmlFile = "dash.mpd";
    vector<Representation> reps = parseMPD(xmlFile);

    for (const auto& rep : reps) {
        cout << "ID: " << rep.id << " | Bandwidth: " << rep.bandwidth << " | Resolution: " 
             << rep.width << "x" << rep.height << " | URL: " << rep.baseURL << endl;
    }
    return 0;
}

解析结果示例:

ID: 1080p | Bandwidth: 5000000 | Resolution: 1920x1080 | URL: https://example.com/dash/1080p/
ID: 720p | Bandwidth: 2500000 | Resolution: 1280x720 | URL: https://example.com/dash/720p/

C++ 下载 DASH 片段

// c++使用 libcurl 下载 DASH 片段
#include <iostream>
#include <fstream>
#include <curl/curl.h>

using namespace std;

size_t WriteCallback(void* contents, size_t size, size_t nmemb, void* userp) {
    ((ofstream*)userp)->write((char*)contents, size * nmemb);
    return size * nmemb;
}

bool downloadSegment(const string& url, const string& filename) {
    CURL* curl = curl_easy_init();
    if (!curl) return false;

    ofstream file(filename, ios::binary);
    if (!file.is_open()) return false;

    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, WriteCallback);
    curl_easy_setopt(curl, CURLOPT_WRITEDATA, &file);
    CURLcode res = curl_easy_perform(curl);
    curl_easy_cleanup(curl);
    file.close();

    return (res == CURLE_OK);
}

int main() {
    string url = "https://example.com/dash/1080p/segment_1.m4s";
    string filename = "segment_1.m4s";

    if (downloadSegment(url, filename)) {
        cout << "Download successful: " << filename << endl;
    } else {
        cerr << "Download failed!" << endl;
    }
    return 0;
}

C++ 生成 DASH MP4 片段

可以使用 FFmpeg 进行 MP4 分片:

ffmpeg -i input.mp4 -c copy -map 0 -f dash -segment_time 4 output.mpd

或者用 GPAC MP4Box

MP4Box -dash 4000 -frag 4000 -segment-name segment_ output.mp4

DASH 播放(C+++WebRTC/SRS)

如果要搭建 DASH 服务器,可以使用 SRS(Simple Realtime Server):

./objs/srs -c conf/dash.conf

SRS 支持 DASH 直播,将流转换为 DASH 格式,MPD 地址类似:

http://yourserver/live/output.mpd

然后可以在前端使用 dash.js 播放:

<video id="video" controls></video>
<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>
<script>
    var player = dashjs.MediaPlayer().create();
    player.initialize(document.getElementById("video"), "http://yourserver/live/output.mpd", true);
</script>

常见问题

1. 为什么 DASH 播放有延迟?

  • DASH 直播默认会有几秒钟缓存,减少卡顿。如果要降低延迟,可以调整 SegmentTimelinebuffering settings

2. DASH 适合 WebRTC 低延迟直播吗?

  • 不适合。DASH 主要用于 VOD 和传统直播,WebRTC 更适合低延迟交互式直播。

总结

  • DASH 是 HTTP 传输协议,不依赖专用流媒体服务器
  • 支持自适应码率,确保流畅播放
  • 适用于 VOD 和直播,兼容各种编码格式
  • 播放器支持需要额外集成(如 dash.js、Shaka Player)

网站公告

今日签到

点亮在社区的每一天
去签到