QT 实现解密m3u8文件

news2024/12/29 4:06:49

文章目录

    • 概要
    • 如何解密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片段大小有关系。

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

python 深度学习 解决遇到的报错问题9

本篇继python 深度学习 解决遇到的报错问题8-CSDN博客 目录 一、can only concatenate str (not "int") to str 二、cant convert np.ndarray of type numpy.object_. The only supported types are: float64, float32, float16, complex64, complex128, int64, in…

Python基础入门例程32-NP32 牛牛的加减器(运算符)

最近的博文&#xff1a; Python基础入门例程31-NP31 团队分组&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程30-NP30 用列表实现队列&#xff08;列表&#xff09;-CSDN博客 Python基础入门例程29-NP29 用列表实现栈&#xff08;列表&#xff09;-CSDN博客 目录…

牛客网刷题-(11)

&#x1f308;个人主页: Aileen_0v0&#x1f525;系列专栏:PYTHON学习系列专栏&#x1f4ab;"没有罗马,那就自己创造罗马~" 目录 (1)输出1-100的所有奇数 (2)计算输入6个数字中正数的个数 (3)递增序列 (4)PUM (1)输出1-100的所有奇数 #输出1-100的所有奇数 x…

使用趋动云部署ChatGLM3-6B模型

使用趋动云部署ChatGLM3-6B模型 1 创建项目2 配置环境 修改代码3 运行代码 1 创建项目 创建项目 进入项目 -> 运行代码 -> 选择资源&#xff08;B1.large&#xff09; 2 配置环境 修改代码 等待开发者工具加载完成 -> 点击 JupyterLab 进入开发环境 打开 termin…

2023.11.4 Idea 配置国内 Maven 源

目录 配置国内 Maven 源 重新下载 jar 包 配置国内 Maven 源 <mirror><id>alimaven</id><name>aliyun maven</name><url>http://maven.aliyun.com/nexus/content/groups/public/</url><mirrorOf>central</mirrorOf> …

velero 集群备份实战

文章目录 velero 集群备份实战velero 架构velero 安装备份mysql集群备份命令查看备份列表 如何恢复&#xff1f;如何卸载&#xff1f;报错处理 velero 集群备份实战 velero 架构 vmware 的产品。velero 是一个CS架构&#xff0c;服务端是一堆CRD, 监听客户端发来的请求。 优…

【多线程】龟兔赛跑

package org.example;public class Race implements Runnable {//胜利者private static String winner;Overridepublic void run() {for(int i0;i<100;i){boolean flag gameOver(i);//如果flag>100,结束比赛if(flag){break;}System.out.println(Thread.currentThread().g…

批量剪辑:高效处理视频文件的图文解析,AI智剪方法

随着视频文件的数量和种类不断增加&#xff0c;传统的视频剪辑方法往往效率低下且费时费力。为了解决这个问题&#xff0c;批量剪辑和AI智剪技术应运而生。在剪辑过程中&#xff0c;AI智剪可自动调整画面质量、音效、色彩等参数&#xff0c;以保证视频质量。它们可以帮助我们高…

uniapp原生插件之安卓文件操作原生插件

插件介绍 安卓文件操作原生插件&#xff0c;读写文件&#xff0c;文件下载等&#xff0c;支持读取移动设备路径等外部存储设备路径&#xff0c;如U盘路径 插件地址 安卓文件操作原生插件 - DCloud 插件市场 超级福利 uniapp 插件购买超级福利 详细使用文档 uniapp 安卓文…

WPF中依赖属性及附加属性的概念及用法

完全来源于十月的寒流&#xff0c;感谢大佬讲解 依赖属性 由依赖属性提供的属性功能 与字段支持的属性不同&#xff0c;依赖属性扩展了属性的功能。 通常&#xff0c;添加的功能表示或支持以下功能之一&#xff1a; 资源数据绑定样式动画元数据重写属性值继承WPF 设计器集成 …

前端找工作好难啊,准备转行了

前言 23年本科应届生&#xff0c;培训机构出来找了2个月&#xff0c;全国海投&#xff0c;3k白干都没人要。 所以经过朋友的推荐&#xff0c;我开始学习网络安全&#xff0c;一共学了大概 5 个多月的时间&#xff0c;今年的 3 月 6 号在长沙找到了一份安全研发的工作&#xff…

虚拟机创建与连接的详细步骤

文章目录 什么是虚拟机&#xff1f;步骤1: 选择虚拟化软件1.1 VirtualBox1.2 VMware Workstation1.3 VMware Player1.4 Hyper-V 步骤2: 创建虚拟机2.1 打开虚拟化软件2.2 创建新虚拟机2.3 配置虚拟机2.4 安装操作系统2.5 启动虚拟机 步骤3: 连接虚拟机3.1 图形用户界面 (GUI)3.…

SWAT-MODFLOW地表水与地下水耦合模型的建模及应用

目录 第一讲 模型原理与层次结构 第二讲 QGIS软件 第三讲 基于QSWATMOD的SWAT-MODFLOW模拟 第四讲 QSWAT模型介绍与建模 第五讲 基于QGIS的数据制备 第六讲 基于CUP的SWAT参数率定 第七讲 MODFLOW模型讲解 第八讲 结果分析 更多应用 耦合模型被应用到很多科学和工程领…

手把手创建第一个 Android 项目

目录 1. 如何创建 Android 项目 2. 编写代码 3. 运行 Android 应用程序 4. Android 项目结构 Android的开发环境搭建好之后,我们就可以开发并运行Android程序了。Android程序是运行在手机等移动设备上的&#xff0c;但是开发 Android 程序却一般不会放在移动设备上完成…

C++:set和map的使用

set和map的使用 1.关联式容器2.key模型和key_value模型3.set3.1一些注意点3.2set的使用3.3习题 4.multiset5.map5.1一些注意点5.2map的使用5.3习题 6.multimap 1.关联式容器 序列式容器&#xff1a;比如我们之前讲的vector、string、list等均为序列式容器&#xff0c;特点是按…

回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测

回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测 目录 回归预测 | Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测效果一览基本介绍程序设计参考资料 效果一览 基本介绍 1.Matlab实现MPA-BP海洋捕食者算法优化BP神经网络多变量回归预测&…

重新思考边缘负载均衡

本文介绍了Netflix在基于轮询的负载均衡的基础上&#xff0c;集成了包括服务器使用率在内的多因素指标&#xff0c;并对冷启动服务器进行了特殊处理&#xff0c;从而优化了负载均衡逻辑&#xff0c;提升了整体业务性能。原文: Rethinking Netflix’s Edge Load Balancing[1] 我…

全功能测试框架 - Carina

Carina是一个基于Java的测试自动化框架&#xff0c;它将所有测试层结合在一起&#xff1a;移动应用程序&#xff08;web、本机、混合&#xff09;、web应用程序、REST服务和数据库。 github&#xff1a;GitHub - zebrunner/carina: Carina automation framework (TestNG): Web…

MySQL(7):单行函数

不同DBMS函数的差异 内置函数&#xff1a; 系统内置的通用函数。 自定义函数&#xff1a; 根据自己的需要编写的函数。 大多数 DBMS 使用&#xff08;||&#xff09;或者&#xff08;&#xff09;来做拼接符&#xff0c;而在 MySQL 中的字符串拼接函数为concat()。 大部分 D…

【rust/esp32】初识slint ui框架并在st7789 lcd上显示

文章目录 说在前面关于slint关于no-std关于dma准备工作相关依赖代码结果参考 说在前面 esp32版本&#xff1a;s3运行环境&#xff1a;no-std开发环境&#xff1a;wsl2LCD模块&#xff1a;ST7789V2 240*280 LCDSlint版本&#xff1a;master分支github地址&#xff1a;这里 关于s…