QT 实现解密m3u8文件

文章目录

    • 概要
    • 如何解密M3U8文件呢
    • 实现思路和代码
      • 序列图
      • 网络请求
      • 解密
    • 结论

概要

视频文件很多已M3U8文件格式来提供,先复习下什么是M3U8文件!用QT的 mutimedia框架来播放视频时,有的视频加载慢,有的视频加载快,为啥?结论再最后

m3u8 文件实质是一个播放列表(playlist),其可能是一个媒体播放列表(Media Playlist),或者是一个主列表(Master Playlist)。但无论是哪种播放列表,其内部文字使用的都是 utf-8 编码。

当 m3u8 文件作为媒体播放列表(Meida Playlist)时,其内部信息记录的是一系列媒体片段资源,顺序播放该片段资源,即可完整展示多媒体资源。其格式如下所示:

#EXTM3U
#EXT-X-TARGETDURATION:10

#EXTINF:9.009,
http://media.example.com/first.ts
#EXTINF:9.009,
http://media.example.com/second.ts
#EXTINF:3.003,
http://media.example.com/third.ts

对于点播来说,客户端只需按顺序下载上述片段资源,依次进行播放即可。而对于直播来说,客户端需要 定时重新请求 该 m3u8 文件,看下是否有新的片段数据需要进行下载并播放。

当 m3u8 作为主播放列表(Master Playlist)时,其内部提供的是同一份媒体资源的多份流列表资源(Variant Stream)。其格式如下所示:

#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=150000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/low/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=240000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/lo_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=440000,RESOLUTION=416x234,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/hi_mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=640000,RESOLUTION=640x360,CODECS="avc1.42e00a,mp4a.40.2"
http://example.com/high/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5"
http://example.com/audio/index.m3u8

该备用流资源指定了多种不同码率,不同格式的媒体播放列表,并且,该备用流资源也可同时提供不同版本的资源内容,比如不同语言的音频文件,不同角度拍摄的视屏文件等等。客户可以根据不同的网络状态选取合适码流的资源,并且最好根据用户喜好选择合适的资源内容。

以上,就是 m3u8 文件的大概内容

如何解密M3U8文件呢

示例:

M3U8文件是一种播放列表文件,用于存储和组织HLS(HTTP Live Streaming)流媒体数据。在M3U8文件中,EXT-X-KEY、URI和IV等字段是用于描述流媒体的关键信息。

EXT-X-KEY: 这个字段用于指定加密密钥的信息。它通常包含一个URI,该URI指向包含密钥的媒体文件。该字段还可能包含其他参数,如密钥的加密算法和密码等。
URI: 这个字段指定了媒体文件的URL地址。它用于告诉播放器从哪个位置获取媒体数据。
IV: 这个字段是初始化向量(Initialization Vector)的缩写,用于加密算法的初始化过程。在HLS流媒体中,每个媒体片段都使用不同的初始化向量进行加密,以确保每个片段的加密是独立的。
这些字段通常以特定的格式出现在M3U8文件中。下面是一个示例:

#EXTM3U  
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=1280000  
http://example.com/stream.m3u8?token=1234567890  
#EXT-X-KEY:METHOD=AES-128, URI="http://example.com/key.txt", IV=0x00000000000000000000000000000001  
#EXT-X-KEY:METHOD=AES-128, URI="http://example.com/key2.txt", IV=0x00000001000000010000000100000002  
#EXT-X-I-FRAME-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=512000, CODECS="mp4a.40.2", RESOLUTION=480x360, FRAME-RATE=15  
#EXTINF:16.733333,
http://example.com/iframe.m3u8?token=abcdefghijklmnopqrstuvwxyz

在上述示例中,#EXTM3U标识了文件为M3U8播放列表的开始。#EXT-X-STREAM-INF指定了流媒体的信息,如节目ID和带宽。http://example.com/stream.m3u8?token=1234567890是媒体文件的URI。接下来的#EXT-X-KEY字段指定了加密密钥的信息,包括加密方法和密钥的URI以及初始化向量(IV)。在这个例子中,有两个密钥,每个密钥对应一个媒体片段。最后,#EXT-X-I-FRAME-STREAM-INF指定了I帧媒体流的信息,包括节目ID、带宽、编解码器、分辨率和帧率等。http://example.com/iframe.m3u8?token=abcdefghijklmnopqrstuvwxyz是I帧媒体文件的URI。

实现思路和代码

序列图

在这里插入图片描述

网络请求

	QNetworkRequest request(url);  //请求m3u8地址
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
    addAllCookie(request);
    QNetworkReply *pNetworkResponse = pManager->get(request);

    m_tsContent.clear();
    m_keyContent.clear();
    m_ivData.clear();

    QObject::connect(pNetworkResponse, &QNetworkReply::finished, [=]{
        if (pNetworkResponse->error() == QNetworkReply::NoError)
        {
            QByteArray bytes = pNetworkResponse->readAll();
            QJsonObject json_object = QJsonDocument::fromJson(bytes).object();
   
            if(json_object["code"].toInt() == 10000)
            {
                if(!json_object["data"].isUndefined())
                {
                    QJsonValue data = json_object["data"];
                    QJsonValue urls = data["videoUrl"];

                    // Test url here
                    QUrl videoUrl(urls["normal"].toString());
                    QNetworkRequest videoRequest(videoUrl);
                    videoRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
                    m_elaptimer.restart();
                    QNetworkReply *pVideoNetworkResponse = pManager->get(videoRequest);
                    connect(pVideoNetworkResponse, &QNetworkReply::finished, [=]{
                        m3u8time = m_elaptimer.elapsed();
                        if(pVideoNetworkResponse->error() == QNetworkReply::NoError) {
                            QByteArray data = pVideoNetworkResponse->readAll();
                            QString m3u8Content = QString::fromUtf8(data);
                            QStringList lines = m3u8Content.split('\n');
                            QString keyLine;
                            QString tsLine;
                            foreach (const QString &line, lines) {
                                if (line.startsWith("#EXT-X-KEY:")) {
                                    keyLine = line;
                                    qDebug() << "[www]: keyline - " << keyLine;
                                } else if (line.startsWith("https:")) {
                                    tsLine = line;
                                    qDebug() << "[www]: tsLine - " << tsLine;
                                    break;
                                    qDebug() << "[www]: test fist snippet and stop";
                                }
                            }

                            QRegularExpression keyRegex("#EXT-X-KEY:METHOD=([A-Za-z0-9-]+),URI=\"([^\"]+)\",IV=([A-Za-z0-9-]+)");
                            QRegularExpressionMatchIterator matchIterator = keyRegex.globalMatch(keyLine);

                            QString method;
                            QString keyUri;
                            QString IVString;

                            if (matchIterator.hasNext()) {
                                QRegularExpressionMatch match = matchIterator.next();
                                method = match.captured(1);
                                keyUri = match.captured(2);
                                IVString = match.captured(3);

                            }

                            qDebug() << "[www] method: " << method;
                            qDebug() << "[www] keyUri: " << keyUri;
                            qDebug() << "[www] tsLine: " << tsLine;
                            qDebug() << "[www] IVString: " << IVString;

                            QByteArray tsData = QByteArray();
                            QByteArray keyData = QByteArray();
                            QByteArray ivData = QByteArray::fromHex(IVString.right(IVString.size() - 2).toLatin1());
                            m_ivData = ivData;
                            qDebug() << "[www] ivData: " << ivData;

                            QString keyContent;
                            QString tsContent;
                            //Get key from uri
                            QUrl keyUrl(keyUri);
                            QNetworkRequest keyQuest(keyUrl);
                            keyQuest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
                            beginKey = m_elaptimer.elapsed();

                            QNetworkReply *keyResponse = pManager->get(keyQuest);
                            connect(keyResponse, SIGNAL(error(QNetworkReply::NetworkError)),
                                    this, SLOT(httpError(QNetworkReply::NetworkError)));

                            connect(keyResponse, &QNetworkReply::finished, [=]{
                                if(keyResponse->error() == QNetworkReply::NoError) {
                                    QByteArray keyData = keyResponse->readAll();
                                    QString keyContent = QString::fromUtf8(keyData);
                                    qDebug() << "[www] keyContent:" << keyContent;
                                    qDebug() << "[www] keyData:" << keyData.toHex();

                                    m_keyContent = keyData;
                                    checkAesPara();
                                } else {
                                    qDebug() << "[xiaole]" <<  keyResponse->errorString() ;
                                }
                            });


                            //Get ts data from uri
                            QUrl tsUrl(tsLine);
                            QNetworkRequest tsRequest(tsUrl);
                            tsRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
                            QNetworkReply *tsResponse = pManager->get(tsRequest);
                            connect(tsResponse, SIGNAL(error(QNetworkReply::NetworkError)),
                                    this, SLOT(httpError(QNetworkReply::NetworkError)));

                            connect(tsResponse, &QNetworkReply::finished, [=]{
                                if(tsResponse->error() == QNetworkReply::NoError) {
                                    QByteArray tsData = tsResponse->readAll();
                                    QString tsContent = QString::fromUtf8(tsData);
                                    m_tsContent = tsData;
                                    checkAesPara();

                                } 
                            });
                        } else {
                        }

                    });
                }
            }
        }

        pNetworkResponse->close();
        pNetworkResponse->deleteLater();
    });

解密

#include "qaesencryption.h"

extern "C" {
    #include <openssl/aes.h>
    #include <openssl/rand.h>

    QByteArray aesDecrypt(const QByteArray& cipherText, const QByteArray& key, const QByteArray& iv) {
        // 创建一个AES解密上下文
        AES_KEY decryptKey;
        AES_set_decrypt_key(reinterpret_cast<const unsigned char*>(key.constData()), 128, &decryptKey);

        // 解密数据
        QByteArray decryptedText(cipherText.size(), Qt::Uninitialized);
        AES_cbc_encrypt(reinterpret_cast<const unsigned char*>(cipherText.constData()),
                        reinterpret_cast<unsigned char*>(decryptedText.data()),
                        cipherText.size(),
                        &decryptKey,
                        const_cast<unsigned char*>(reinterpret_cast<const unsigned char*>(iv.data())),
                        AES_DECRYPT);

        return decryptedText;
    }
}


QByteArray qaesDecrypt(const QByteArray& cipherText, const QByteArray& key, const QByteArray& iv) {
    return QAESEncryption::Decrypt(QAESEncryption::AES_128, QAESEncryption::CBC, cipherText, key,
                   iv, QAESEncryption::ZERO);
}
checkAesPara()
{
    if(!m_tsContent.isEmpty() &&  !m_keyContent.isEmpty())
    {
        qDebug()<<"keyContent"<< m_keyContent <<"tsContent"<< m_tsContent.length();
        QByteArray decTsData = aesDecrypt(m_tsContent, m_keyContent, m_ivData);
        QFile file("/home/test.ts");  //保存到本地文件
        if (file.open(QIODevice::ReadWrite | QIODevice::Truncate))
        {
            file.write(decTsData);
            file.close();
        }
        writeOver  = m_elaptimer.elapsed();
        writeTime = writeOver-jiemiOver;
    }
}

结论

`
● ts片段越大,缓冲时间越长,反之亦然
● qt播放器并不需要一个完整的ts下载完才开始播放。

所以视频加载快慢,最大的因素是网速,还有就是跟TS片段大小有关系。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/119579.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

深入了解Jedis:Java操作Redis的常见类型数据存储

目录 前言 一、Jedis介绍 1.Jedis在各方面的功能 2.特点 二、Java连接Redis 1.导入pom依赖 2.建立连接 三、Java操作Redis的常见类型数据存储 1.字符串 2.哈希表 3.列表 4.集合 5.有序集合 四、Redis的实际应用场景实例 1.会议信息实体 2.自定义注解 3.创建切面…

mermaid学习第一天/更改主题颜色和边框颜色/《需求解释流程图》

mermaid 在线官网&#xff1a; https://mermaid-js.github.io/ 在线学习文件&#xff1a; https://mermaid.js.org/syntax/quadrantChart.html 1、今天主要是想做需求解释的流程图&#xff0c;又不想自己画&#xff0c;就用了&#xff0c;框框不能直接进行全局配置&#xff0…

Mac电脑录屏软件 Screen Recorder by Omi 中文最新

Screen Recorder by Omi是一款屏幕录制软件&#xff0c;它可以帮助用户轻松地录制屏幕活动&#xff0c;并将其保存为高质量的视频文件。 该软件提供了多种录制选项&#xff0c;包括全屏录制、选择区域录制和单窗口录制等&#xff0c;同时提供了丰富的设置选项&#xff0c;如视…

[动态规划] (十) 路径问题 LeetCode 174.地下城游戏

[动态规划] (十) 路径问题: LeetCode 174.地下城游戏 文章目录 [动态规划] (十) 路径问题: LeetCode 174.地下城游戏题目解析解题思路状态表示状态转移方程初始化和填表顺序返回值 代码实现总结 174. 地下城游戏 题目解析 先明白下题题再来看。 [动态规划] (四) LeetCode 91.…

Apache Doris (五十一): Doris数据缓存

🏡 个人主页:IT贫道_大数据OLAP体系技术栈,Apache Doris,Clickhouse 技术-CSDN博客 🚩 私聊博主:加入大数据技术讨论群聊,获取更多大数据资料。 🔔 博主个人B栈地址:豹哥教你大数据的个人空间-豹哥教你大数据个人主页-哔哩哔哩视频 目录 1.

【教3妹学编程-算法题】2924. 找到冠军 II

3妹&#xff1a;2哥快看&#xff0c;我黑龙江的闺蜜给我发了一个她在打雪仗的视频&#xff0c;好大的雪啊&#xff0c;好欢乐。 2哥&#xff1a;什么&#xff0c;东北不是暴雪吗&#xff0c; 还可以打雪仗。 3妹 :是啊&#xff0c;可是雪停了就可以打雪仗了啊。 2哥&#xff1a…

竞赛选题 深度学习手势识别算法实现 - opencv python

文章目录 1 前言2 项目背景3 任务描述4 环境搭配5 项目实现5.1 准备数据5.2 构建网络5.3 开始训练5.4 模型评估 6 识别效果7 最后 1 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 深度学习手势识别算法实现 - opencv python 该项目较为新颖…

DVWA - 1

文章目录 Brute Forcelowhigh Command Injectionlowmediumhigh CSRFlowmediumhigh Brute Force low 1.进入到Brute Force页面&#xff0c;随机输入一个用户名及密码&#xff0c;点击登录。使用 BurpSuite查看拦截历史&#xff0c;找到该登录请求&#xff0c;右键send to intr…

互联网医院|湖南互联网医院|解决医疗资源不足问题

随着科技的进步和互联网的普及&#xff0c;互联网医院作为一种新型的医疗模式&#xff0c;逐渐受到人们的关注和认可。本文将详细介绍互联网医院的功能和优势&#xff0c;帮助大家全面了解这种新型的医疗服务。 一、互联网医院的功能 1、在线问诊&#xff1a;互联网医院为患者…

[CISCN2019 华北赛区 Day2 Web1]Hack World1

提示 基于布尔的盲注使用python脚本跑 这里已经提示flag在flag表在flag字段 首先输入1 2都能有回显 每当这个时候第一想到的都应该是基于布尔的盲注是否能使用 尝试fuzz 通过fuzz大概知道后续思路 应为过滤的比较全面所以放弃联合查询 报错查询 预设置 使用基于布尔的盲注…

在CSDN上挣点外快的小tips

作为一个在csdn上也挣了一点辛苦费的博主&#xff0c;个人简单总结了两个方法。 1、道德的方法 如上图&#xff0c;可以把自己曾经做过的一些设计或其它资源类的内容&#xff0c;打包传到CSDN的资源池中&#xff0c;有条件的可以写个文章引流一下&#xff0c;运气好的话会有人下…

axios 全局错误处理和请求取消

这两个功能都是用拦截器实现。 前景提要&#xff1a; ts 简易封装 axios&#xff0c;统一 API 实现在 config 中配置开关拦截器 全局错误处理 在构造函数中&#xff0c;添加一个响应拦截器即可。在构造函数中注册拦截器的好处是&#xff0c;无论怎么实例化封装类&#xff0c…

【数智化人物展】觉非科技CEO李东旻:数据闭环,智能驾驶数智时代发展的新引擎...

李东旻 本文由觉非科技CEO李东旻投递并参与《2023中国企业数智化转型升级先锋人物》榜单/奖项评选。 大数据产业创新服务媒体 ——聚焦数据 改变商业 数智化的主要作用是帮助决策。它的核心是大数据&#xff0c;以大数据为基础&#xff0c;匹配合适的AI技术&#xff0c;促使数…

list、numpy、tensor之间相互转化

参考博客【精选】python 中各类型介绍及相互转换 - list, array, tensor, dict, tuple, DataFrame_dict转tensor-CSDN博客 1 # list -> numpy scores np.array(scores) # list -> numpy 2 # numpy -> tensor scores torch.tensor(scores) # numpy -> tensor…

css 图片好玩的一个属性,添加滤镜

鼠标经过效果对比&#xff1a; 上图是改变了图片的饱和度&#xff0c;代码如下&#xff1a; .img-box .v-image:hover {filter: saturate(1.75); }其他滤镜说明如下图&#xff1a;

apache-tomcat-9.0.29 安装配置教程

链接&#xff1a;https://pan.baidu.com/s/100buXYpn8w8xjI2KdvHk2Q?pwd2mwc 提取码&#xff1a;2mwc 1.将压缩包解压到指定文件夹下 2.进入bin文件夹下 3.找到setclasspath.bat文件 4.推荐用notepad打开文件&#xff0c;并做如下配置&#xff08;可解决tomcat启动闪退问题&…

Juniper Networks Junos OS EX远程命令执行漏洞(CVE-2023-36845)

Juniper Networks Junos OS EX远程命令执行漏洞&#xff08;CVE-2023-36845&#xff09; 免责声明漏洞描述漏洞影响漏洞危害网络测绘Fofa: body"J-web" || title"Juniper Web Device Manager" 漏洞复现1. 构造poc2. 查看文件3. 执行命令 免责声明 仅用于技…

阿里微服务质量保障系列:故障演练

对于很多大型企业(如阿里巴巴)来说,经过多年的技术演进,系统工具和架构已经高度垂直化,服务器规模也达到了比较大的体量。当服务规模大于一定量(如10000台)时,小概率的硬件故障每天都会发生。这时如果需要人的干预,系统就无法可靠的伸缩。 为此每一层的系统都会面向失…

如何使用 NFTScan NFT API 在 Arbitrum 网络上开发 Web3 应用

Arbitrum 是以太坊的 Layer 2 扩容方案&#xff0c;为以太坊面临的高 gas 费和网络拥堵问题&#xff0c;提供了一个解决方案。作为 Layer 1 的以太坊基础层受每秒只能验算 15 笔交易的限制&#xff0c;在目前以太坊使用需求庞大的情况下&#xff0c;局限了以太坊的可扩展性。Ar…

C# Onnx Dense Face 3D人脸重建,人脸Mesh

效果 项目 代码 using OpenCvSharp; using System; using System.Collections.Generic; using System.Drawing; using System.Windows.Forms;namespace Onnx_Demo {public partial class frmMain : Form{public frmMain(){InitializeComponent();}string fileFilter "*.…