Mercurial
diff mrjunejune/generate_hls_sample.c @ 242:543df0fe7168
[tools] Add full HLS player support
| author | MrJuneJune <me@mrjunejune.com> |
|---|---|
| date | Mon, 03 Aug 2026 13:14:41 -0700 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mrjunejune/generate_hls_sample.c Mon Aug 03 13:14:41 2026 -0700 @@ -0,0 +1,174 @@ +#include "third_party/ffmpeg/ffmpeg_cli.h" + +#include <dirent.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <unistd.h> + +static int remove_flat_directory(const char *directory) +{ + DIR *entries = opendir(directory); + if (!entries) + return errno == ENOENT ? 0 : -1; + + struct dirent *entry; + while ((entry = readdir(entries)) != NULL) + { + if (strcmp(entry->d_name, ".") == 0 || + strcmp(entry->d_name, "..") == 0) + continue; + char path[1024]; + int length = snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name); + if (length < 0 || (size_t)length >= sizeof(path) || unlink(path) != 0) + { + closedir(entries); + return -1; + } + } + closedir(entries); + return rmdir(directory); +} + +static int write_master_playlist(const char *directory) +{ + char path[1024]; + int length = snprintf(path, sizeof(path), "%s/master.m3u8", directory); + if (length < 0 || (size_t)length >= sizeof(path)) + return -1; + + FILE *playlist = fopen(path, "wb"); + if (!playlist) + return -1; + const char *content = + "#EXTM3U\n" + "#EXT-X-VERSION:7\n" + "#EXT-X-STREAM-INF:BANDWIDTH=1200000,AVERAGE-BANDWIDTH=1000000," + "CODECS=\"avc1.4d401f,mp4a.40.2\"\n" + "h264-stream.m3u8\n" + "#EXT-X-STREAM-INF:BANDWIDTH=900000,AVERAGE-BANDWIDTH=750000," + "CODECS=\"vp09.00.10.08,opus\"\n" + "vp9-stream.m3u8\n"; + size_t content_length = strlen(content); + int result = fwrite(content, 1, content_length, playlist) == content_length + ? 0 + : -1; + fclose(playlist); + return result; +} + +static const char *workspace_path( + const char *path, + char *buffer, + size_t buffer_size) +{ + const char *workspace = getenv("BUILD_WORKSPACE_DIRECTORY"); + if (!workspace || path[0] == '/') + return path; + int length = snprintf(buffer, buffer_size, "%s/%s", workspace, path); + return length < 0 || (size_t)length >= buffer_size ? NULL : buffer; +} + +int main(int argc, char **argv) +{ + if (argc != 3) + { + fprintf(stderr, "Usage: %s <input.mp4> <output-directory>\n", argv[0]); + return 2; + } + + char final_buffer[1024]; + const char *final_directory = workspace_path( + argv[2], + final_buffer, + sizeof(final_buffer)); + if (!final_directory) + { + fprintf(stderr, "HLS output path is too long.\n"); + return 1; + } + + char stage_template[1100]; + int stage_length = snprintf( + stage_template, + sizeof(stage_template), + "%s.stage-XXXXXX", + final_directory); + if (stage_length < 0 || (size_t)stage_length >= sizeof(stage_template) || + !mkdtemp(stage_template)) + { + fprintf(stderr, "Unable to create the HLS staging directory.\n"); + return 1; + } + + Fmp4HlsOpts options = ffmpeg_fmp4_hls_opts_default(); + options.codec = FFMPEG_HLS_VP9_OPUS; + FfmpegResult result = ffmpeg_video_to_fmp4_hls( + argv[1], + stage_template, + "vp9", + &options); + if (result == FFMPEG_OK) + { + options.codec = FFMPEG_HLS_H264_AAC; + options.video_bitrate = 1200; + options.audio_bitrate = 128; + result = ffmpeg_video_to_fmp4_hls( + argv[1], + stage_template, + "h264", + &options); + } + if (result == FFMPEG_OK) + { + options.use_mpeg_ts = true; + result = ffmpeg_video_to_fmp4_hls( + argv[1], + stage_template, + "h264-ts", + &options); + } + if (result != FFMPEG_OK || write_master_playlist(stage_template) != 0) + { + fprintf(stderr, "%s\n", ffmpeg_last_error()); + remove_flat_directory(stage_template); + return 1; + } + + char backup_directory[1100]; + int backup_length = snprintf( + backup_directory, + sizeof(backup_directory), + "%s.backup", + final_directory); + if (backup_length < 0 || (size_t)backup_length >= sizeof(backup_directory)) + { + remove_flat_directory(stage_template); + return 1; + } + remove_flat_directory(backup_directory); + + bool had_previous = rename(final_directory, backup_directory) == 0; + if (!had_previous && errno != ENOENT) + { + remove_flat_directory(stage_template); + return 1; + } + if (rename(stage_template, final_directory) != 0) + { + if (had_previous) + rename(backup_directory, final_directory); + remove_flat_directory(stage_template); + return 1; + } + if (had_previous && remove_flat_directory(backup_directory) != 0) + { + fprintf(stderr, "Generated HLS sample, but could not remove its backup.\n"); + return 1; + } + + printf("Generated fMP4 and MPEG-TS HLS samples from %s in %s\n", argv[1], final_directory); + return 0; +}