文章目录
前言
在当今的互联网时代,流媒体技术已经成为传输音视频内容的核心技术之一。为了更好地理解和应用流媒体技术,开始学习并实现了一个高性能的HTTP-FLV流媒体服务器。HTTP-FLV是一种常用的实时流媒体传输协议,结合了HTTP协议的广泛兼容性和FLV(Flash Video)格式的高效性,非常适用于实时视频流的传输。在博客中,将详细介绍HTTP-FLV的基本概念,并分享在实现高性能HTTP-FLV流媒体服务器过程中积累的经验和技巧。
一、HTTP-FLV是什么?
FLV (Flash Video) 是 Adobe 公司推出的另一种视频格式,是一种在网络上传输的流媒体数据存储容器格式。其格式相对简单轻量,不需要很大的媒体头部信息。整个 FLV 由 The FLV Header, The FLV Body 以及其它 Tag 组成。因此加载速度极快。采用 FLV 格式封装的文件后缀为 .flv。
HTTP-FLV 依靠 MIME 的特性,根据协议中的 Content-Type 来选择相应的程序去处理相应的内容,使得流媒体可以通过 HTTP 传输。相较于 RTMP 协议,HTTP-FLV 能够好的穿透防火墙,它是基于 HTTP/80 传输,有效避免被防火墙拦截。除此之外,它可以通过 HTTP 302 跳转灵活调度/负载均衡,支持使用 HTTPS 加密传输,也能够兼容支持 Android,iOS 的移动端。
说了这么多优点,也来顺便说下 HTTP-FLV 的缺点,由于它的传输特性,会让流媒体资源缓存在本地客户端,在保密性方面不够好。因为网络流量较大,它也不适合做拉流协议。
地址是以http://开头的,是基于HTTP协议的,HTTP-FLV可以简单地理解为RTMP的HTTP协议版本。但HTTP-FLV协议一般只能用拉流观看,HTTP-FLV协议的延迟也是比较低的,大概在1-3秒左右。HTTP-FLV相对于RTMP适配更多的播放场景。
HTTP-FLV直播流一般需要加入插件才能播放,如网页需要引入flv.js后,浏览器才能播放HTTP-FLV直播流。B站开源的flv.js促进了HTTP-FLV在浏览器的普及。
一般HTTP-FLV的流媒体服务,推流是以RTMP协议,拉流是用HTTP-FLV协议
HTTP-FLV 使用类似 RTMP流式的 HTTP 长连接,需由特定流媒体服务器分发的,兼顾两者的优点。以及可以复用现有 HTTP 分发资源的流式协议。它的实时性和 RTMP 相等,与 RTMP 相比又省去了部分协议交互时间,首屏时间更短,可拓展的功能也更多。
二、实现一个高性能的HTTP-FLV流媒体服务器
1.main.cpp
代码及详细注释如下:
#include "Server/BoostServer.h"
#include "Scheduler.h"
#include "Utils/Config.h"
/*
ffmpeg 播放http-flv视频流
ffplay -i http://localhost:8080/test.flv
*/
int main(int argc, char* argv[])
{
const char* file = NULL;
file = "config.json"; // 配置文件,启动参数放置在config.json
Config config(file); // 利用第三方库jsoncpp解析config.json
if (!config.state) {
// 配置参数读取失败
printf("failed to read config file: %s\n", file);
return -1;
}
/*
BoostServer server(&config);: 这行代码创建了一个名为 server 的 BoostServer 对象,
并使用名为 config 的参数进行初始化。
std::thread([&]() { ... }).detach();: 这是一个 std::thread 对象的构造,
它使用了一个 lambda 表达式作为线程的执行体。
lambda 表达式 [&]() 表示捕获外部作用域的所有变量(以引用方式),
{ ... } 中的代码是线程的主体部分。
在 lambda 表达式内部,server.start(); 调用了 BoostServer 对象的 start() 方法,这是启动服务器的操作。
detach() 方法将新创建的线程分离,意味着它会在后台运行,
与主线程独立执行,不会等待该线程的结束。这样做是为了使主线程继续执行而不被阻塞。
*/
BoostServer server(&config);
std::thread([&]() {
server.start();
}).detach();
Scheduler sch(&server,&config);
sch.loop();
return 0;
}
2.配置相关类
代码及详细注释如下:
1. config.json
// config.json
{
"ip": "0.0.0.0",
"port": 8080,
"threadNum": 1
}
2. config.h
#ifndef CONFIG_H
#define CONFIG_H
#include <string>
class Config
{
public:
Config(const char* file);
Config() = delete;
~Config();
public:
bool state = false;
void show();
const char* getIp() const {
return ip.data();
}
int getPort() const {
return port;
}
int getThreadNum() const {
return threadNum;
}
private:
std::string mFile;
std::string ip;
int port;
int threadNum;
};
#endif //CONFIG_H
3. config.cpp
#include "Config.h"
#include <fstream>
#include <json/json.h>
#include "Log.h"
Config::Config(const char* file) : mFile(file) // 传递配置文件的文件路径
{
std::ifstream ifs(mFile, std::ios::binary);
if (!ifs.is_open()) {
LOGE("open %s error", file);
return;
}
else {
Json::CharReaderBuilder builder;
builder["collectComments"] = true;
JSONCPP_STRING errs;
Json::Value root;
if (parseFromStream(builder, ifs, &root, &errs)) {
this->ip = root["ip"].asString();
this->port = root["port"].asInt();
this->threadNum = root["threadNum"].asInt();
state = true;
}
else {
LOGE("parse %s error", file);
}
ifs.close();
}
}
Config::~Config()
{
}
void Config::show() {
//printf("config.file=%s\n", mFile.data());
}
3.BoostServer
1. BoostServer.h
#ifndef BOOSTSERVER_H
#define BOOSTSERVER_H
#include "boost.h"
#include <map>
class Config;
class HttpServerConnection;
class BoostServer
{
public:
BoostServer(Config* config);
~BoostServer();
public:
void start();
bool addConn(HttpServerConnection* conn); // 添加连接
bool removeConn(std::string session);
Ht