Skip to content

Instantly share code, notes, and snippets.

@nxtreaming
Last active October 11, 2016 13:39
Show Gist options
  • Save nxtreaming/2c6eb9c03fd82b23b80f to your computer and use it in GitHub Desktop.
Save nxtreaming/2c6eb9c03fd82b23b80f to your computer and use it in GitHub Desktop.
A simple service to loop RTMP VOD streaming
/*
* loop_rtmp.c: A simple service to loop RTMP VOD streaming
*
* Usage:
* loop_rtmp <playlist> <channel>
*
*/
#include <unistd.h>
#include <stdint.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "rtmp_config.h"
#include "sys_utils.h"
#include "task_exec.h"
#define LOCAL_LOG_IDENTIFY "loop_rtmp/loop_rtmp"
static int check_playlist(const char *playlist)
{
FILE *fp = fopen(playlist, "r");
char *buffer,*p, *ptr;
char line[2048] = {0};
char filename[2048];
int total_size = 1024*2048;
int len = 0;
struct stat file_stat;
if (!fp)
return -1;
buffer = malloc(total_size);
if (!buffer) {
fclose(fp);
return -2;
}
p = buffer;
//
// the playlist format:
// ....
// file /main_disk/live_record/imbatv_1500K-1422099849.mp4
// file /main_disk/live_record/imbatv_1500K-1422171768.mp4
// ....
//
while (fgets(line, sizeof(line) - 1 , fp) != NULL) {
// Only work for *nix
ptr = strchr(line, '/');
if (ptr) {
strcpy(filename, ptr);
ptr = filename;
while(*ptr) {
// remove tail "\n" or "\r"
if (*ptr == '\n' || *ptr == '\r') {
*ptr = '\0';
break;
}
ptr++;
}
// check if file is available
// MUST check the path is a regular file, not a directory
if (access(filename, F_OK) != -1 &&
(stat(filename, &file_stat) == 0 && S_ISREG(file_stat.st_mode))) {
int n = strlen(line);
if (len + n > total_size)
break;
strncpy(p, line, n);
len += n;
p += n;
}
}
}
fclose(fp);
fp = fopen(playlist, "w");
if (fp) {
fwrite(buffer, 1, len, fp);
fclose(fp);
}
free(buffer);
return 0;
}
//
// The playlist is published to "rtmp://127.0.0.1/vod/$channel"
//
int main(int argc, const char *argv[])
{
pid_t pid = 0;
char input_url[MAX_URL_LENGTH] = {0};
char rtmp_url[MAX_URL_LENGTH] = {0};
char channel[128] = {0};
char logfile[256] = {0};
char cmd[2048] = {0};
int i=0, max_loop_count = 256;
if (argc != 3) {
fprintf(stderr, "Usage: loop_rtmp <playlist> <channel>\n");
exit(1);
}
snprintf(channel, sizeof(channel), "%s", argv[2]);
ptr = strstr(channel, PLATFORM_MAGIC_CODE);
if (!ptr) {
fprintf(stderr, "Channel code(%s) is wrong\n", channel);
return -1;
}
*ptr = '\0';
snprintf(input_url, MAX_URL_LENGTH, "%s", argv[1]);
snprintf(rtmp_url, MAX_URL_LENGTH, "rtmp://127.0.0.1/vod/%s", channel);
fprintf(stderr, "input_url: %s\n", input_url);
fprintf(stderr, "loop_channel: %s\n", rtmp_url);
fprintf(stderr, "\n");
while (i < max_loop_count) {
//
// kill all subscribers which are listening to current channel
// otherwise, the timestamp is wrong when re-loop, we will get the following huge warnings:
//
// DTS 4203501128, next:2082712000 st:0 invalid dropping
// PTS 4203501208, next:2082712000 invalid dropping st:0
// DTS 4203501168, next:2082752000 st:0 invalid dropping
// PTS 4203501368, next:2082752000 invalid dropping st:0
// ....
//
// until the remote publish server close client's connection
//
// Another solution: repeat/extend contents in the playlist to 21 days
//
kill_apps(FFMPEG_APP, rtmp_url);
make_log_name(LOG_BASE_PATH, LOCAL_LOG_IDENTIFY, channel, logfile, sizeof(logfile));
//
// Make sure the files in playlist are available, otherwise, we will get the following error:
//
// [concat @ 0x29f60c0] Impossible to open '/main_disk/live_record/imbatv_1500K-1421920601.mp4'
// /main_disk/logs/mp4_1500K_playlist.txt: No such file or directory
//
check_playlist(input_url);
snprintf(cmd, sizeof(cmd), "/usr/local/bin/%s -loglevel warning -xerror -re -f concat -i '%s' -c copy -metadata title='loop_%s' -f flv '%s' &> '%s'", FFMPEG_APP, inp
ut_url, argv[2], rtmp_url, logfile);
fprintf(stderr, "ffmpeg_cmd: %s\n", cmd);
pid = task_exec(cmd, 0);
// TODO: playlist refresh automatically
waitpid(pid, &status, 0);
// can not capture: SIGKILL and SIGSTOP
if (WIFSIGNALED(status) && WTERMSIG(status) == SIGSEGV) {
fprintf(stderr, "terminated forcely");
break;
}
i++;
// Must sleep, because playlist list is a local file which will not be blocked by network IO
sleep(1);
fprintf(stderr, "\n***** loop playlist: count=%d *****\n", i);
}
fprintf(stderr, "The App has been terminated: loop_count=%d\n", i);
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment