comparison 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
comparison
equal deleted inserted replaced
241:9c2eec61a152 242:543df0fe7168
1 #include "third_party/ffmpeg/ffmpeg_cli.h"
2
3 #include <dirent.h>
4 #include <errno.h>
5 #include <stdio.h>
6 #include <stdlib.h>
7 #include <string.h>
8 #include <sys/stat.h>
9 #include <unistd.h>
10
11 static int remove_flat_directory(const char *directory)
12 {
13 DIR *entries = opendir(directory);
14 if (!entries)
15 return errno == ENOENT ? 0 : -1;
16
17 struct dirent *entry;
18 while ((entry = readdir(entries)) != NULL)
19 {
20 if (strcmp(entry->d_name, ".") == 0 ||
21 strcmp(entry->d_name, "..") == 0)
22 continue;
23 char path[1024];
24 int length = snprintf(path, sizeof(path), "%s/%s", directory, entry->d_name);
25 if (length < 0 || (size_t)length >= sizeof(path) || unlink(path) != 0)
26 {
27 closedir(entries);
28 return -1;
29 }
30 }
31 closedir(entries);
32 return rmdir(directory);
33 }
34
35 static int write_master_playlist(const char *directory)
36 {
37 char path[1024];
38 int length = snprintf(path, sizeof(path), "%s/master.m3u8", directory);
39 if (length < 0 || (size_t)length >= sizeof(path))
40 return -1;
41
42 FILE *playlist = fopen(path, "wb");
43 if (!playlist)
44 return -1;
45 const char *content =
46 "#EXTM3U\n"
47 "#EXT-X-VERSION:7\n"
48 "#EXT-X-STREAM-INF:BANDWIDTH=1200000,AVERAGE-BANDWIDTH=1000000,"
49 "CODECS=\"avc1.4d401f,mp4a.40.2\"\n"
50 "h264-stream.m3u8\n"
51 "#EXT-X-STREAM-INF:BANDWIDTH=900000,AVERAGE-BANDWIDTH=750000,"
52 "CODECS=\"vp09.00.10.08,opus\"\n"
53 "vp9-stream.m3u8\n";
54 size_t content_length = strlen(content);
55 int result = fwrite(content, 1, content_length, playlist) == content_length
56 ? 0
57 : -1;
58 fclose(playlist);
59 return result;
60 }
61
62 static const char *workspace_path(
63 const char *path,
64 char *buffer,
65 size_t buffer_size)
66 {
67 const char *workspace = getenv("BUILD_WORKSPACE_DIRECTORY");
68 if (!workspace || path[0] == '/')
69 return path;
70 int length = snprintf(buffer, buffer_size, "%s/%s", workspace, path);
71 return length < 0 || (size_t)length >= buffer_size ? NULL : buffer;
72 }
73
74 int main(int argc, char **argv)
75 {
76 if (argc != 3)
77 {
78 fprintf(stderr, "Usage: %s <input.mp4> <output-directory>\n", argv[0]);
79 return 2;
80 }
81
82 char final_buffer[1024];
83 const char *final_directory = workspace_path(
84 argv[2],
85 final_buffer,
86 sizeof(final_buffer));
87 if (!final_directory)
88 {
89 fprintf(stderr, "HLS output path is too long.\n");
90 return 1;
91 }
92
93 char stage_template[1100];
94 int stage_length = snprintf(
95 stage_template,
96 sizeof(stage_template),
97 "%s.stage-XXXXXX",
98 final_directory);
99 if (stage_length < 0 || (size_t)stage_length >= sizeof(stage_template) ||
100 !mkdtemp(stage_template))
101 {
102 fprintf(stderr, "Unable to create the HLS staging directory.\n");
103 return 1;
104 }
105
106 Fmp4HlsOpts options = ffmpeg_fmp4_hls_opts_default();
107 options.codec = FFMPEG_HLS_VP9_OPUS;
108 FfmpegResult result = ffmpeg_video_to_fmp4_hls(
109 argv[1],
110 stage_template,
111 "vp9",
112 &options);
113 if (result == FFMPEG_OK)
114 {
115 options.codec = FFMPEG_HLS_H264_AAC;
116 options.video_bitrate = 1200;
117 options.audio_bitrate = 128;
118 result = ffmpeg_video_to_fmp4_hls(
119 argv[1],
120 stage_template,
121 "h264",
122 &options);
123 }
124 if (result == FFMPEG_OK)
125 {
126 options.use_mpeg_ts = true;
127 result = ffmpeg_video_to_fmp4_hls(
128 argv[1],
129 stage_template,
130 "h264-ts",
131 &options);
132 }
133 if (result != FFMPEG_OK || write_master_playlist(stage_template) != 0)
134 {
135 fprintf(stderr, "%s\n", ffmpeg_last_error());
136 remove_flat_directory(stage_template);
137 return 1;
138 }
139
140 char backup_directory[1100];
141 int backup_length = snprintf(
142 backup_directory,
143 sizeof(backup_directory),
144 "%s.backup",
145 final_directory);
146 if (backup_length < 0 || (size_t)backup_length >= sizeof(backup_directory))
147 {
148 remove_flat_directory(stage_template);
149 return 1;
150 }
151 remove_flat_directory(backup_directory);
152
153 bool had_previous = rename(final_directory, backup_directory) == 0;
154 if (!had_previous && errno != ENOENT)
155 {
156 remove_flat_directory(stage_template);
157 return 1;
158 }
159 if (rename(stage_template, final_directory) != 0)
160 {
161 if (had_previous)
162 rename(backup_directory, final_directory);
163 remove_flat_directory(stage_template);
164 return 1;
165 }
166 if (had_previous && remove_flat_directory(backup_directory) != 0)
167 {
168 fprintf(stderr, "Generated HLS sample, but could not remove its backup.\n");
169 return 1;
170 }
171
172 printf("Generated fMP4 and MPEG-TS HLS samples from %s in %s\n", argv[1], final_directory);
173 return 0;
174 }