c++ hls协议 m3u8解析器

从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

 

 

 

 

 

 

 

  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

路边闲人2

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值