308 lines
8.0 KiB
C++
308 lines
8.0 KiB
C++
#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, ×tamp, &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
|