stream-deploy/ZLM/3rdpart/media-server/libhls/demo/hls-server.cpp

308 lines
8.0 KiB
C++
Raw Normal View History

#include "aio-worker.h"
#include "aio-socket.h"
#include "mpeg-ps.h"
#include "mpeg-ts.h"
#include "hls-m3u8.h"
#include "hls-media.h"
#include "hls-param.h"
#include "flv-proto.h"
#include "flv-reader.h"
#include "flv-demuxer.h"
#include "http-server.h"
#include "http-route.h"
#include "sys/thread.h"
#include "sys/system.h"
#include "sys/path.h"
#include "cstringext.h"
#include "utf8codec.h"
#include <string.h>
#include <assert.h>
#include <map>
#include <list>
#include <atomic>
#include <vector>
#include <string>
#define CWD "d:\\video\\"
extern "C" int http_list_dir(http_session_t* session, const char* path);
struct hls_ts_t
{
std::atomic<int> ref;
void* data;
size_t size;
std::string name;
};
struct hls_playlist_t
{
pthread_t t;
std::string file;
hls_media_t* hls;
hls_m3u8_t* m3u8;
int64_t pts;
int64_t last_pts;
uint8_t packet[2 * 1024 * 1024];
int i;
std::list<hls_ts_t*> files;
};
static std::map<std::string, hls_playlist_t*> s_playlists;
static int hls_handler(void* param, const void* data, size_t bytes, int64_t pts, int64_t /*dts*/, int64_t duration)
{
hls_playlist_t* playlist = (hls_playlist_t*)param;
int discontinue = 0;
if (playlist->i > 0)
{
discontinue = (playlist->last_pts + HLS_DURATION * 1000 < pts/*discontinue*/ || pts + duration + HLS_DURATION * 1000 < playlist->pts/*rewind*/) ? 1 : 0;
}
playlist->pts = pts;
playlist->last_pts = pts + duration;
char name[128] = { 0 };
snprintf(name, sizeof(name) - 1, "%s/%d.ts", playlist->file.c_str(), playlist->i++);
hls_m3u8_add(playlist->m3u8, name, pts, duration, discontinue);
// add new segment
hls_ts_t* ts = new hls_ts_t;
ts->ref = 1;
ts->name = name;
ts->size = bytes;
ts->data = malloc(bytes);
memcpy(ts->data, data, bytes);
playlist->files.push_back(ts);
// remove oldest segment
while(playlist->files.size() > HLS_LIVE_NUM + 1)
{
ts = playlist->files.front();
playlist->files.pop_front();
if (0 == std::atomic_fetch_sub(&ts->ref, 1) - 1)
{
free(ts->data);
delete ts;
}
}
printf("new segment: %s\n", name);
return 0;
}
static int flv_handler(void* param, int codec, const void* data, size_t bytes, uint32_t pts, uint32_t dts, int flags)
{
hls_media_t* hls = (hls_media_t*)param;
switch (codec)
{
case FLV_AUDIO_AAC:
return hls_media_input(hls, PSI_STREAM_AAC, data, bytes, pts, dts, 0);
case FLV_AUDIO_MP3:
return hls_media_input(hls, PSI_STREAM_MP3, data, bytes, pts, dts, 0);
case FLV_VIDEO_H264:
return hls_media_input(hls, PSI_STREAM_H264, data, bytes, pts, dts, flags ? HLS_FLAGS_KEYFRAME : 0);
case FLV_VIDEO_H265:
return hls_media_input(hls, PSI_STREAM_H265, data, bytes, pts, dts, flags ? HLS_FLAGS_KEYFRAME : 0);
default:
// nothing to do
return 0;
}
}
static int STDCALL hls_server_worker(void* param)
{
int r, type;
size_t taglen;
uint32_t clock;
uint32_t timestamp;
hls_playlist_t* playlist = (hls_playlist_t*)param;
std::string file = playlist->file + ".flv";
UTF8Decode utf8(file.c_str());
std::string fullpath = CWD;
fullpath += utf8;
while (1)
{
void* flv = flv_reader_create(fullpath.c_str());
flv_demuxer_t* demuxer = flv_demuxer_create(flv_handler, playlist->hls);
clock = 0;
while (1 == flv_reader_read(flv, &type, &timestamp, &taglen, playlist->packet, sizeof(playlist->packet)))
{
uint32_t now = system_clock();
if (0 == clock)
{
clock = now;
}
else
{
if (timestamp > now - clock)
system_sleep(timestamp - (now - clock));
}
r = flv_demuxer_input(demuxer, type, playlist->packet, taglen, timestamp);
assert(0 == r);
}
flv_demuxer_destroy(demuxer);
flv_reader_destroy(flv);
}
hls_media_destroy(playlist->hls);
//hls_m3u8_destroy(playlist->m3u8);
//s_playlists.erase();
//delete playlist;
return thread_destroy(playlist->t);
}
static int hls_server_m3u8(http_session_t* session, const std::string& path)
{
char playlist[8 * 1024];
hls_m3u8_t* m3u8 = s_playlists.find(path)->second->m3u8;
assert(m3u8);
assert(0 == hls_m3u8_playlist(m3u8, 0, playlist, sizeof(playlist)));
http_server_set_header(session, "content-type", HLS_M3U8_TYPE);
http_server_reply(session, 200, playlist, strlen(playlist));
printf("load %s.m3u8 file\n", path.c_str());
return 0;
}
static int hls_server_ts_onsend(void* param, int code, size_t bytes)
{
hls_ts_t* ts = (hls_ts_t*)param;
if (0 == std::atomic_fetch_sub(&ts->ref, 1) - 1)
{
free(ts->data);
delete ts;
}
return 0;
}
static int hls_server_ts(http_session_t* session, const std::string& path, const std::string& ts)
{
hls_playlist_t* playlist = s_playlists.find(path)->second;
assert(playlist);
std::list<hls_ts_t*>::iterator i;
std::string file = path + '/' + ts;
for(i = playlist->files.begin(); i != playlist->files.end(); ++i)
{
hls_ts_t* ts = *i;
if(ts->name == file)
{
std::atomic_fetch_add(&ts->ref, 1);
http_server_send(session, ts->data, ts->size, hls_server_ts_onsend, ts);
printf("load file %s\n", file.c_str());
return 0;
}
}
printf("load ts file(%s) failed\n", file.c_str());
http_server_set_status_code(session, 404, NULL);
return http_server_send(session, "", 0, NULL, NULL);
}
static int hls_server_onlive(void* /*http*/, http_session_t* session, const char* /*method*/, const char* path)
{
// HTTP CORS
http_server_set_header(session, "Access-Control-Allow-Origin", "*");
http_server_set_header(session, "Access-Control-Allow-Headers", "*");
http_server_set_header(session, "Access-Control-Allow-Credentials", "true");
http_server_set_header(session, "Access-Control-Allow-Methods", "GET, POST, PUT, OPTIONS, CONNECT");
path = path + 6;
if (strendswith(path, ".m3u8"))
{
std::string app(path, strlen(path) - 5);
if (s_playlists.find(app) == s_playlists.end())
{
hls_playlist_t* playlist = new hls_playlist_t();
playlist->file = app;
playlist->m3u8 = hls_m3u8_create(HLS_LIVE_NUM, 3);
playlist->hls = hls_media_create(HLS_DURATION * 1000, hls_handler, playlist);
playlist->i = 0;
s_playlists[app] = playlist;
thread_create(&playlist->t, hls_server_worker, playlist);
}
return hls_server_m3u8(session, app);
}
else if (strendswith(path, ".ts"))
{
const char* ts = strchr(path, '/');
std::string app(path, ts ? ts - path : strlen(path));
if (ts && s_playlists.find(app) != s_playlists.end())
{
return hls_server_ts(session, app, ts + 1);
}
}
http_server_set_status_code(session, 404, NULL);
return http_server_send(session, "", 0, NULL, NULL);
}
static int hls_server_onvod(void* /*http*/, http_session_t* session, const char* /*method*/, const char* path)
{
UTF8Decode utf8(path + 5 /* /vod/ */);
std::string fullpath = CWD;
fullpath += utf8;
printf("hls_server_onvod: %s\n", fullpath.c_str());
if (path_testdir(fullpath.c_str()))
{
return http_list_dir(session, fullpath.c_str());
}
else if (path_testfile(fullpath.c_str()))
{
//http_server_set_header(session, "Transfer-Encoding", "chunked");
if (std::string::npos != fullpath.find(".m3u8"))
http_server_set_header(session, "content-type", HLS_M3U8_TYPE);
else if (std::string::npos != fullpath.find(".mpd"))
http_server_set_header(session, "content-type", "application/dash+xml");
else if (std::string::npos != fullpath.find(".mp4") || std::string::npos != fullpath.find(".m4v"))
http_server_set_header(session, "content-type", "video/mp4");
else if (std::string::npos != fullpath.find(".m4a"))
http_server_set_header(session, "content-type", "audio/mp4");
return http_server_sendfile(session, fullpath.c_str(), NULL, NULL);
}
http_server_set_status_code(session, 404, NULL);
return http_server_send(session, "", 0, NULL, NULL);
}
void hls_server_test(const char* ip, int port)
{
aio_worker_init(4);
http_server_t* http = http_server_create(ip, port);
http_server_set_handler(http, http_server_route, http);
http_server_addroute("/live/", hls_server_onlive);
http_server_addroute("/vod/", hls_server_onvod);
// http process
while('q' != getchar())
{
}
http_server_destroy(http);
aio_worker_clean(4);
}
#if defined(_HLS_SERVER_TEST_)
int main(int argc, char* argv[])
{
hls_server_test(NULL, 80);
return 0;
}
#endif