从zlmediakit这个项目中借鉴来的代码,可以直接加到项目中使用。
用到了c++11的语法。
不管是直播还是点播都可以使用。
接收到m3u8文件的内容后,调用 HlsParser::parse(m_url, m_m3u8Str); 进行处理,处理结束后自动回调onParsed函数
hlsparser.h
#ifndef HTTP_HLSPARSER_H
#define HTTP_HLSPARSER_H
#include <map>
#include <list>
#include <string>
typedef struct{
//url地址
std::string url;
//ts切片长度
float duration;
//内嵌m3u8//
//节目id
int program_id;
//带宽
int bandwidth;
//宽度
int width;
//高度
int height;
} ts_segment;
class HlsParser {
public:
HlsParser(){}
~HlsParser(){}
bool parse(const std::string &http_url,const std::string &m3u8);
/**
* 是否存在#EXTM3U字段,是否为m3u8文件
*/
bool isM3u8() const;
/**
* #EXT-X-ALLOW-CACHE值,是否允许cache
*/
bool allowCache() const;
/**
* 是否存在#EXT-X-ENDLIST字段,是否为直播
*/
bool isLive() const ;
/**
* #EXT-X-VERSION值,版本号
*/
int getVersion() const;
/**
* #EXT-X-TARGETDURATION字段值
*/
int getTargetDur() const;
/**
* #EXT-X-MEDIA-SEQUENCE字段值,该m3u8序号
*/
int getSequence() const;
/**
* 内部是否含有子m3u8
*/
bool isM3u8Inner() const;
protected:
//解析出ts文件地址回调
virtual void onParsed(bool is_m3u8_inner, int64_t sequence, const std::map<int,ts_segment> &ts_list) {};
private:
bool _is_m3u8 = false;
bool _allow_cache = false;
bool _is_live = true;
int _version = 0;
int _target_dur = 0;
float _total_dur = 0;
int64_t _sequence = 0;
//每部是否有m3u8
bool _is_m3u8_inner = false;
};
#endif //HTTP_HLSPARSER_H
hlsparser.cpp
#include "hlsparser.h"
#include <vector>
#include <string.h>
using namespace std;
namespace Parser
{
//将字符串分割成多个字符串的vector
std::vector<std::string> split(const std::string& s, const char *delim)
{
std::vector<std::string> ret;
int last = 0;
int index = s.find(delim, last);
while (index != string::npos)
{
if (index - last > 0)
{
ret.push_back(s.substr(last, index - last));
}
last = index + strlen(delim);
index = s.find(delim, last);
}
if (!s.size() || s.size() - last > 0)
{
ret.push_back(s.substr(last));
}
return ret;
}
//去除前后的空格、回车符、制表符...
std::string& trim(std::string &s, const std::string &chars = " \r\n\t")
{
string map(0xFF, '\0');
for (auto &ch : chars)
{
map[(unsigned char &)ch] = '\1';
}
while (s.size() && map.at((unsigned char &)s.back())) s.pop_back();
while (s.size() && map.at((unsigned char &)s.front())) s.erase(0, 1);
return s;
}
//不分大小写的字符串比较函数
struct StrCaseCompare
{
bool operator()(const string &__x, const string &__y) const
{
#ifdef WIN32
return _stricmp(__x.data(), __y.data()) < 0;
#else
return strcasecmp(__x.data(), __y.data()) < 0;//<string.h>
#endif
}
};
class StrCaseMap : public multimap<std::string, std::string, StrCaseCompare>
{
public:
typedef multimap<string, string, StrCaseCompare> Super;
StrCaseMap() = default;
~StrCaseMap() = default;
string &operator[](const string &k)
{
auto it = find(k);
if (it == end())
{
it = Super::emplace(k, "");
}
return it->second;
}
template <typename V>
void emplace(const string &k, V &&v)
{
auto it = find(k);
if (it != end())
{
return;
}
Super::emplace(k, std::forward<V>(v));
}
template <typename V>
void emplace_force(const string k, V &&v)
{
Super::emplace(k, std::forward<V>(v));
}
};
string FindField(const char* buf, const char* start, const char *end, int bufSize = 0)
{
if (bufSize <= 0)
{
bufSize = strlen(buf);
}
const char *msg_start = buf, *msg_end = buf + bufSize;
int len = 0;
if (start != NULL)
{
len = strlen(start);
msg_start = strstr(buf, start);
}
if (msg_start == NULL)
{
return "";
}
msg_start += len;
if (end != NULL)
{
msg_end = strstr(msg_start, end);
if (msg_end == NULL)
{
return "";
}
}
return string(msg_start, msg_end);
}
StrCaseMap parseArgs(const std::string &str, const char *pair_delim="&", const char *key_delim="=")
{
StrCaseMap ret;
auto arg_vec = Parser::split(str, pair_delim);
for (string &key_val : arg_vec)
{
auto key = FindField(key_val.data(), NULL, key_delim);
auto val = FindField(key_val.data(), key_delim, NULL);
ret.emplace_force(trim(key), trim(val));
}
return ret;
}
};
bool HlsParser::parse(const string &http_url, const string &m3u8)
{
float extinf_dur = 0;
ts_segment segment;
map<int, ts_segment> ts_map;
_total_dur = 0;
_is_live = true;
_is_m3u8_inner = false;
int index = 0;
auto lines = Parser::split(m3u8, "\n");
for (auto &line : lines)
{
Parser::trim(line);
if (line.size() < 2) {
continue;
}
if ((_is_m3u8_inner || extinf_dur != 0) && line[0] != '#') {
segment.duration = extinf_dur;
if (line.find("http://") == 0 || line.find("https://") == 0) {
segment.url = line;
} else {
if (line.find("/") == 0) {
segment.url = http_url.substr(0, http_url.find("/", 8)) + line;
} else {
segment.url = http_url.substr(0, http_url.rfind("/") + 1) + line;
}
}
if (!_is_m3u8_inner) {
//ts按照先后顺序排序
ts_map.emplace(index++, segment);
} else {
//子m3u8按照带宽排序
ts_map.emplace(segment.bandwidth, segment);
}
extinf_dur = 0;
continue;
}
_is_m3u8_inner = false;
if (line.find("#EXTINF:") == 0) {
sscanf(line.data(), "#EXTINF:%f,", &extinf_dur);
_total_dur += extinf_dur;
continue;
}
static const string s_stream_inf = "#EXT-X-STREAM-INF:";
if (line.find(s_stream_inf) == 0) {
_is_m3u8_inner = true;
auto key_val = Parser::parseArgs(line.substr(s_stream_inf.size()), ",", "=");
segment.program_id = atoi(key_val["PROGRAM-ID"].data());
segment.bandwidth = atoi(key_val["BANDWIDTH"].data());
sscanf(key_val["RESOLUTION"].data(), "%dx%d", &segment.width, &segment.height);
continue;
}
if (line == "#EXTM3U") {
_is_m3u8 = true;
continue;
}
if (line.find("#EXT-X-ALLOW-CACHE:") == 0) {
_allow_cache = (line.find(":YES") != string::npos);
continue;
}
if (line.find("#EXT-X-VERSION:") == 0) {
sscanf(line.data(), "#EXT-X-VERSION:%d", &_version);
continue;
}
if (line.find("#EXT-X-TARGETDURATION:") == 0) {
sscanf(line.data(), "#EXT-X-TARGETDURATION:%d", &_target_dur);
continue;
}
if (line.find("#EXT-X-MEDIA-SEQUENCE:") == 0) {
sscanf(line.data(), "#EXT-X-MEDIA-SEQUENCE:%lld", &_sequence);
continue;
}
if (line.find("#EXT-X-ENDLIST") == 0) {
//点播
_is_live = false;
continue;
}
continue;
}
if (_is_m3u8)
{
onParsed(_is_m3u8_inner, _sequence, ts_map);
}
return _is_m3u8;
}
bool HlsParser::isM3u8() const {
return _is_m3u8;
}
bool HlsParser::isLive() const{
return _is_live;
}
bool HlsParser::allowCache() const {
return _allow_cache;
}
int HlsParser::getVersion() const {
return _version;
}
int HlsParser::getTargetDur() const {
return _target_dur;
}
int HlsParser::getSequence() const {
return _sequence;
}
bool HlsParser::isM3u8Inner() const {
return _is_m3u8_inner;
}
使用示例:
class HlsGetter: public HttpClientImp, HlsParser
{
private:
string m_m3u8Str;
int64_t m_recvedSize;
public:
//获取http body
virtual void onResponseBody(const char *buf, int64_t datalen, int64_t totalSize) override
{
m_m3u8Str.append(buf, datalen);
m_recvedSize += datalen;
//解析m3u8,回调函数在onParsed()
int ret = HlsParser::parse(m_url, m_m3u8Str);
if (!ret)
{
printf("m3u8 parse failed: %s\n", m_m3u8Str.c_str());
}
}
//处理m3u8的结果
virtual void onParsed(bool is_m3u8_inner, int64_t sequence, const std::map<int, ts_segment> &ts_map) override
{
if (!is_m3u8_inner)
{
std::vector<std::string> tsVec;
for (auto &item : ts_map)
{
auto &segment = item.second;
tsVec.push_back(item.second.url);
}
sucess(tsVec);
}
else//m3u8列表
{
//选择第一个节目(最高清的?)
if (ts_map.size() == 0)
{
failed(-1);
return;
}
auto new_url = ts_map.rbegin()->second.url;
printf("hls redirect:%s\n", new_url.c_str());
rediret(new_url);
}
}
}
接收到m3u8文件的内容后,调用 HlsParser::parse(m_url, m_m3u8Str); 进行处理,处理结束后回调onParsed函数
reference:
https://github.com/xia-chu/ZLMediaKit/tree/master/src/Http