#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 #include #include #include #include #include #include #define CWD "d:\\video\\" extern "C" int http_list_dir(http_session_t* session, const char* path); struct hls_ts_t { std::atomic 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 files; }; static std::map 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::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