├── .gitignore ├── CMakeLists.txt ├── segmon.cfg ├── detectFFMpeg.cmake ├── README.md ├── segmenter.h ├── helpers.c ├── segmon.c ├── options_parsing.c └── segmenter.c /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles/ 3 | Makefile 4 | cmake_install.cmake 5 | segmenter 6 | tests 7 | segmon 8 | sdrclib_build/ 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | project(httplvs-sd) 2 | cmake_minimum_required(VERSION 2.6) 3 | ADD_DEFINITIONS (-Wall) 4 | include(detectFFMpeg.cmake) 5 | 6 | include_directories(../) 7 | add_subdirectory(../sdrclib sdrclib_build) 8 | 9 | add_executable(segmenter segmenter.c options_parsing.c helpers.c ) 10 | target_link_libraries(segmenter -lm -lavformat -lavcodec -lavutil ${FFMPEG_LIBS}) 11 | 12 | add_executable(segmon segmon.c ) 13 | target_link_libraries(segmon sdrclib) 14 | -------------------------------------------------------------------------------- /segmon.cfg: -------------------------------------------------------------------------------- 1 | stream=bond 2 | input=tcp://127.0.0.1:5000 3 | distdir=/storage/sputnik/bond 4 | segmentlen=20m 5 | listl=a 6 | 7 | stream=enc1 8 | input=tcp://127.0.0.1:5001 9 | distdir=/storage/sputnik/encoder1 10 | segmentlen=20m 11 | listl=a 12 | 13 | stream=enc2 14 | input=tcp://127.0.0.1:5002 15 | distdir=/storage/sputnik/encoder2 16 | segmentlen=20m 17 | listl=a 18 | 19 | stream=enc3 20 | input=tcp://127.0.0.1:5003 21 | distdir=/storage/sputnik/encoder3 22 | segmentlen=20m 23 | listl=a 24 | 25 | stream=enc4 26 | input=tcp://127.0.0.1:5004 27 | distdir=/storage/sputnik/encoder4 28 | segmentlen=20m 29 | listl=a 30 | 31 | -------------------------------------------------------------------------------- /detectFFMpeg.cmake: -------------------------------------------------------------------------------- 1 | message( STATUS "detecting ffmpeg ...." ) 2 | #TODO: Add more flexible way to detect/ 3 | execute_process ( 4 | COMMAND pkg-config --libs libavformat libavcodec libavutil 5 | COMMAND sed -e "s/-lavformat//" -e "s/-lavcodec//" -e "s/-lavutil//" 6 | OUTPUT_VARIABLE FFMPEG_LIBS 7 | OUTPUT_STRIP_TRAILING_WHITESPACE 8 | ) 9 | string (REPLACE " " " " FFMPEG_LIBS "${FFMPEG_LIBS}") 10 | string (REPLACE " " " " FFMPEG_LIBS "${FFMPEG_LIBS}") 11 | string (REPLACE " " " " FFMPEG_LIBS "${FFMPEG_LIBS}") 12 | string (STRIP "${FFMPEG_LIBS}" FFMPEG_LIBS) 13 | message(STATUS "Needed extra ffmpeg libs: ${FFMPEG_LIBS}" ) 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # iPhone HLS Segmenter 2 | 3 | The segmenter will take an VIDEO input stream (a ffmpeg url, file or stdin) and packet-copy it to HLS segments. 4 | It will also generate .m3u8 playlist 5 | 6 | If output is under httpd document root files are ready to stream. 7 | 8 | The input should be MPEG TS. The old code dealing with .mp3 and raw aac streams was removed! 9 | 10 | 11 | This project is based on old version of http://www.ioncannon.net/projects/http-live-video-stream-segmenter-and-distributor/ 12 | which was heavily modified for stability. It is possible to segment "live" streams from stdin or any ffmpegable url to tmpfs under 13 | httpd with limited segment numbers 14 | 15 | ## HOW TO COMPILE 16 | 17 | Install latest FFMPEG version(libav is is probably not supportd), cmake 2.6+ and go to the directory where CMakeLists.txt is located. 18 | 19 | $ cmake . 20 | $ make 21 | $ make install 22 | 23 | 24 | for example usage try segmenter -h 25 | 26 | 27 | ## DESCRIPTION 28 | 29 | This project is an attempt to make it easier to set up a live streaming server using Apple's HTTP streaming protocol. 30 | 31 | 32 | ## REQUIREMENTS 33 | 34 | FFMpeg is the only external requirement 35 | 36 | 37 | ## LICENSE 38 | 39 | Copyright (c) 2014 Stoian Ivanov 40 | Copyright (c) 2009 Carson McDonald 41 | Copyright (c) 2009 Chase Douglas 42 | 43 | This program is free software; you can redistribute it and/or 44 | modify it under the terms of the GNU General Public License version 2 45 | as published by the Free Software Foundation. 46 | 47 | This program is distributed in the hope that it will be useful, 48 | but WITHOUT ANY WARRANTY; without even the implied warranty of 49 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 50 | GNU General Public License for more details. 51 | 52 | You should have received a copy of the GNU General Public License 53 | along with this program; if not, write to the Free Software 54 | Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 55 | -------------------------------------------------------------------------------- /segmenter.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Stoian Ivanov 3 | * Copyright (c) 2009 Chase Douglas 4 | * 5 | * This program is free software; you can redistribute it and/or 6 | * modify it under the terms of the GNU General Public License version 2 7 | * as published by the Free Software Foundation. 8 | * 9 | * This program is distributed in the hope that it will be useful, 10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 | * GNU General Public License for more details. 13 | * 14 | * You should have received a copy of the GNU General Public License 15 | * along with this program; if not, write to the Free Software 16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 17 | */ 18 | 19 | /********************************************************************************** 20 | * This code is part of HTTP-Live-Video-Stream-Segmenter-and-Distributor 21 | * look for newer versions at github.com 22 | **********************************************************************************/ 23 | #ifndef segmenter_h_a22d54e85a2948bbb2c2219e34c7c168_included 24 | #define segmenter_h_a22d54e85a2948bbb2c2219e34c7c168_included 25 | 26 | #ifdef __cplusplus 27 | extern "C" { 28 | #endif 29 | 30 | #include 31 | 32 | #define PROGRAM_VERSION "0.2" 33 | 34 | #define MAX_SEGMENTS 2048 35 | #define MAX_FILENAME_LENGTH 2048 36 | #define MAXT_EXT_LENGTH 9 37 | //type of output to perform 38 | #define OUTPUT_STREAM_AUDIO 1 39 | #define OUTPUT_STREAM_VIDEO 2 40 | #define OUTPUT_STREAM_AV (OUTPUT_STREAM_AUDIO | OUTPUT_STREAM_VIDEO) 41 | 42 | #if USE_OLD_FFMPEG 43 | #define AV_PKT_FLAG_KEY PKT_FLAG_KEY 44 | #define AVMEDIA_TYPE_AUDIO CODEC_TYPE_AUDIO 45 | #define AVMEDIA_TYPE_VIDEO CODEC_TYPE_VIDEO 46 | #endif 47 | 48 | 49 | //options_parsing.c: 50 | void printBanner(); 51 | void printUsage(); 52 | int parseCommandLine( 53 | int argc, const char* argv[], char* inputFile, char* outputFile, char* baseDir, char* baseName, char* baseExtension, 54 | int* segmentLength, int* listlen, int* quiet, int* version, int* usage, int *persistent 55 | ); 56 | 57 | 58 | //helpers.c: 59 | void ffmpeg_version(); 60 | void debugReturnCode(int r); 61 | 62 | #define FNHOLDER(name) char name[MAX_FILENAME_LENGTH+1];name[MAX_FILENAME_LENGTH]=0; 63 | 64 | #ifdef __cplusplus 65 | } 66 | #endif 67 | #endif 68 | -------------------------------------------------------------------------------- /helpers.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2014 Stoian Ivanov 3 | * 4 | * This program is free software; you can redistribute it and/or 5 | * modify it under the terms of the GNU General Public License version 2 6 | * as published by the Free Software Foundation. 7 | * 8 | * This program is distributed in the hope that it will be useful, 9 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | * GNU General Public License for more details. 12 | * 13 | * You should have received a copy of the GNU General Public License 14 | * along with this program; if not, write to the Free Software 15 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 16 | */ 17 | 18 | /********************************************************************************** 19 | * This code is part of HTTP-Live-Video-Stream-Segmenter-and-Distributor 20 | * look for newer versions at github.com 21 | **********************************************************************************/ 22 | #include "segmenter.h" 23 | 24 | #include 25 | #include "libavformat/avformat.h" 26 | 27 | void ffmpeg_version() { 28 | printBanner(); 29 | // output build and version numbers 30 | fprintf(stderr, "libav versions:\n"); 31 | fprintf(stderr, " libavutil version: %s\n", AV_STRINGIFY(LIBAVUTIL_VERSION)); 32 | fprintf(stderr, " libavutil build: %d\n", LIBAVUTIL_BUILD); 33 | fprintf(stderr, " libavcodec version: %s\n", AV_STRINGIFY(LIBAVCODEC_VERSION)); 34 | fprintf(stdout, " libavcodec build: %d\n", LIBAVCODEC_BUILD); 35 | fprintf(stderr, " libavformat version: %s\n", AV_STRINGIFY(LIBAVFORMAT_VERSION)); 36 | fprintf(stderr, " libavformat build: %d\n", LIBAVFORMAT_BUILD); 37 | 38 | fprintf(stderr, "This tool is version " PROGRAM_VERSION ", built on " __DATE__ " " __TIME__); 39 | #ifdef __GNUC__ 40 | fprintf(stderr, ", with gcc: " __VERSION__ "\n"); 41 | #else 42 | fprintf(stderr, ", using a non-gcc compiler\n"); 43 | #endif 44 | } 45 | 46 | 47 | 48 | void debugReturnCode(int r) { 49 | switch (r) { 50 | #if USE_OLD_FFMPEG 51 | case AVERROR_UNKNOWN: 52 | #else 53 | case AVERROR(EINVAL): 54 | #endif 55 | /**< unknown error */ 56 | fprintf(stderr, "Unknown error.\n"); 57 | break; 58 | #if USE_OLD_FFMPEG 59 | case AVERROR_IO: 60 | #else 61 | case AVERROR(EIO): 62 | #endif 63 | /**< I/O error */ 64 | fprintf(stderr, "I/O error.\n"); 65 | break; 66 | #if USE_OLD_FFMPEG 67 | case AVERROR_NUMEXPECTED: 68 | #else 69 | case AVERROR(EDOM): 70 | #endif 71 | /**< Number syntax expected in filename. */ 72 | fprintf(stderr, "Number syntax expected in filename.\n"); 73 | break; 74 | case AVERROR_INVALIDDATA: 75 | /**< invalid data found */ 76 | fprintf(stderr, "Invalid data found.\n"); 77 | break; 78 | #if USE_OLD_FFMPEG 79 | case AVERROR_NOMEM: 80 | #else 81 | case AVERROR(ENOMEM): 82 | #endif 83 | /**< not enough memory */ 84 | fprintf(stderr, "Not enough memory.\n"); 85 | break; 86 | #if USE_OLD_FFMPEG 87 | case AVERROR_NOFMT: 88 | #else 89 | case AVERROR(EILSEQ): 90 | #endif 91 | /**< unknown format */ 92 | fprintf(stderr, "Unknown format.\n"); 93 | break; 94 | #if USE_OLD_FFMPEG 95 | case AVERROR_NOTSUPP: 96 | #else 97 | case AVERROR(ENOSYS): 98 | #endif 99 | /**< Operation not supported. */ 100 | fprintf(stderr, "Operation not supported.\n"); 101 | break; 102 | #if USE_OLD_FFMPEG 103 | case AVERROR_NOENT: 104 | #else 105 | case AVERROR(ENOENT): 106 | #endif 107 | /**< No such file or directory. */ 108 | fprintf(stderr, "No such file or directory.\n"); 109 | break; 110 | case AVERROR_EOF: 111 | /**< End of file. */ 112 | fprintf(stderr, "End of file.\n"); 113 | break; 114 | case AVERROR_PATCHWELCOME: 115 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 116 | fprintf(stderr, "Not yet implemented in FFmpeg. Patches welcome.\n"); 117 | break; 118 | #if USE_OLD_FFMPEG 119 | /**< Codes For Old FFMPEG Deprecated. */ 120 | #else 121 | /**< New Error Codes For FFMPEG. */ 122 | case AVERROR_BUG: 123 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 124 | fprintf(stderr, "Internal bug. AVERROR_BUG\n"); 125 | break; 126 | #ifdef AVERROR_BUG2 127 | case AVERROR_BUG2: 128 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 129 | fprintf(stderr, "Internal bug. AVERROR_BUG2.\n"); 130 | break; 131 | #endif 132 | case AVERROR_STREAM_NOT_FOUND: 133 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 134 | fprintf(stderr, "Stream not found.\n"); 135 | break; 136 | case AVERROR_PROTOCOL_NOT_FOUND: 137 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 138 | fprintf(stderr, "Protocol not found.\n"); 139 | break; 140 | case AVERROR_OPTION_NOT_FOUND: 141 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 142 | fprintf(stderr, "Option not found.\n"); 143 | break; 144 | case AVERROR_MUXER_NOT_FOUND: 145 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 146 | fprintf(stderr, "Muxer not found. \n"); 147 | break; 148 | case AVERROR_FILTER_NOT_FOUND: 149 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 150 | fprintf(stderr, "Filter not found.\n"); 151 | break; 152 | case AVERROR_EXIT: 153 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 154 | fprintf(stderr, "Immediate exit was requested; the called function should not be restarted.\n"); 155 | break; 156 | case AVERROR_ENCODER_NOT_FOUND: 157 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 158 | fprintf(stderr, "Encoder not found.\n"); 159 | break; 160 | case AVERROR_DEMUXER_NOT_FOUND: 161 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 162 | fprintf(stderr, "Demuxer not found.\n"); 163 | break; 164 | case AVERROR_DECODER_NOT_FOUND: 165 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 166 | fprintf(stderr, "Decoder not found.\n"); 167 | break; 168 | case AVERROR_BSF_NOT_FOUND: 169 | /**< Not yet implemented in FFmpeg. Patches welcome. */ 170 | fprintf(stderr, "Bitstream filter not found.\n"); 171 | break; 172 | #endif 173 | default: 174 | fprintf(stderr, "Unknown return code: %d\n", r); 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /segmon.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "sdrclib/easyparse.h" 12 | #include "sdrclib/daemonize.h" 13 | 14 | #define CFGFILE "segmon.cfg" 15 | 16 | const char * cfgfile=CFGFILE; 17 | 18 | #define MAXPARAMS 20 19 | #define SEGLSZ 20 20 | 21 | typedef struct { 22 | int inuse; 23 | pid_t child; 24 | int argc ; 25 | const char * strm; 26 | const char * argv[MAXPARAMS+3]; 27 | char segl[SEGLSZ]; 28 | } segmenter_t; 29 | 30 | #define MAXSEGMETERS 200 31 | segmenter_t segmenters [MAXSEGMETERS+1]; 32 | segmenter_t *csegm; 33 | 34 | #define DEFSEGMENTER "./segmenter" 35 | const char *segmenter; 36 | 37 | int easyparse_cb (void*userdata, char *name, int namel, char *value, int valuel){ 38 | printf ("K=V: [%.*s]=[%.*s]\n",namel,name,valuel,value); 39 | if (value && valuel)value[valuel]=0; 40 | if (namel==9 && memcmp(name,"segmenter",9)==0) { 41 | segmenter=value; 42 | } else if (namel==6 && memcmp(name,"stream",6)==0) { 43 | if (!csegm) { 44 | csegm=segmenters; 45 | } else if(csegm-segmentersinuse=1; 52 | csegm->strm=value; 53 | csegm->argc=4; 54 | csegm->argv[0]=segmenter; 55 | csegm->argv[1]="-o"; 56 | csegm->argv[2]=value; 57 | csegm->argv[3]="-p"; 58 | } else if (namel==7 && memcmp(name,"plsfile",7)==0) {//fix -o value: 59 | csegm->argv[2]=value; 60 | } else if (csegm && namel==5 && memcmp(name,"input",5)==0) { 61 | if (csegm->argcargv[csegm->argc++]="-i"; 63 | csegm->argv[csegm->argc++]=value; 64 | } else { 65 | printf ("too many segmenter params! (hard limit is %d)\n",MAXPARAMS); 66 | return 1; 67 | } 68 | } else if (csegm && namel==7 && memcmp(name,"distdir",7)==0){ 69 | if (csegm->argcargv[csegm->argc++]="-d"; 71 | csegm->argv[csegm->argc++]=value; 72 | } else { 73 | printf ("too many segmenter params! (hard limit is %d)\n",MAXPARAMS); 74 | return 1; 75 | } 76 | } else if (csegm && namel==7 && memcmp(name,"segname",7)==0){ 77 | if (csegm->argcargv[csegm->argc++]="-f"; 79 | csegm->argv[csegm->argc++]=value; 80 | } else { 81 | printf ("too many segmenter params! (hard limit is %d)\n",MAXPARAMS); 82 | return 1; 83 | } 84 | } else if (csegm && namel==5 && memcmp(name,"listl",5)==0){ 85 | if (csegm->argcargv[csegm->argc++]="-m"; 87 | csegm->argv[csegm->argc++]=value; 88 | if (valuel==1 && *value=='a') csegm->argv[1]="-f"; 89 | } else { 90 | printf ("too many segmenter params! (hard limit is %d)\n",MAXPARAMS); 91 | return 1; 92 | } 93 | } else if (csegm && namel==10 && memcmp(name,"segmentlen",10)==0 && valuel>0){ 94 | if (csegm->argcargv[csegm->argc++]="-l"; 102 | if (mult==1){ 103 | csegm->argv[csegm->argc++]=value; 104 | } else { 105 | snprintf(csegm->segl,SEGLSZ-1,"%d",atoi(value)*mult); 106 | csegm->argv[csegm->argc++]=csegm->segl; 107 | } 108 | } else { 109 | printf ("too many segmenter params! (hard limit is %d)\n",MAXPARAMS); 110 | return 1; 111 | } 112 | } 113 | 114 | return 0; 115 | } 116 | 117 | void cleanchilds(pid_t pid){ 118 | 119 | int x; 120 | for(x=0;xsi_code == CLD_EXITED) { 134 | int status; 135 | if ( waitpid(sinfo->si_pid, &status, WNOHANG) != -1){ 136 | if (WIFEXITED(status)||WIFSIGNALED(status))cleanchilds(sinfo->si_pid); 137 | } 138 | } 139 | errno = sav_errno; 140 | } 141 | #define usage(pname) { fprintf (stderr,"Usage: %s [-c ] [-p ]\n Default config is "CFGFILE" passing -p trigers deamon mode",pname); exit (1); } 142 | int main (int argc,char * argv[] ){ 143 | segmenter=DEFSEGMENTER; 144 | memset(segmenters,0,sizeof(segmenters)); 145 | csegm=NULL; 146 | 147 | 148 | int c; 149 | char * pidfile=NULL; 150 | while ((c = getopt (argc, argv, "c:p:")) != -1) switch (c) { 151 | case 'c': 152 | cfgfile = optarg; 153 | break; 154 | case 'p': 155 | pidfile = optarg; 156 | break; 157 | case '?': 158 | usage (argv[0]); 159 | break; 160 | default: 161 | usage (argv[0]); 162 | } 163 | 164 | printf("using scfg file %s\n",cfgfile); 165 | struct stat st; 166 | char *cfg; 167 | if (stat(cfgfile, &st)!=0){ 168 | printf("can't stat scfg file %s\n",cfgfile); 169 | exit (-1); 170 | } else { 171 | cfg=malloc(st.st_size+2); 172 | if (!cfg) { 173 | printf("failed to allocate memory for cfg?!\n"); 174 | exit(-1); 175 | } 176 | cfg[st.st_size]=0; 177 | } 178 | FILE *cfgf=fopen(cfgfile,"r"); 179 | if (!cfgf) { 180 | printf("failed to open %s for reading!\n",cfgfile); 181 | exit(-1); 182 | } 183 | size_t ret=fread(cfg,1,st.st_size,cfgf); 184 | if (ret!=st.st_size){ 185 | printf("failed to read %ld bytes from %s!\n",st.st_size,cfgfile); 186 | exit(-1); 187 | } 188 | fclose(cfgf); 189 | 190 | easyparse(cfg,st.st_size,easyparse_cb,NULL); 191 | 192 | if (pidfile) daemonize("/dev/null","/dev/null","/dev/null",pidfile); 193 | 194 | struct sigaction sa; 195 | 196 | sa.sa_flags = SA_SIGINFO; 197 | sa.sa_sigaction = handle_sigchld; 198 | sigemptyset(&sa.sa_mask); 199 | if (sigaction(SIGCHLD, &sa, NULL) == -1) 200 | { 201 | perror("sigaction"); 202 | exit(EXIT_FAILURE); 203 | } 204 | 205 | sa.sa_flags = SA_SIGINFO; 206 | sa.sa_sigaction = handle_sigterm; 207 | sigemptyset(&sa.sa_mask); 208 | if (sigaction(SIGHUP, &sa, NULL) == -1) { 209 | perror("sigaction"); 210 | exit(EXIT_FAILURE); 211 | } 212 | sigemptyset(&sa.sa_mask); 213 | if (sigaction(SIGINT, &sa, NULL) == -1) { 214 | perror("sigaction"); 215 | exit(EXIT_FAILURE); 216 | } 217 | sigemptyset(&sa.sa_mask); 218 | if (sigaction(SIGQUIT, &sa, NULL) == -1) { 219 | perror("sigaction"); 220 | exit(EXIT_FAILURE); 221 | } 222 | sigemptyset(&sa.sa_mask); 223 | if (sigaction(SIGABRT, &sa, NULL) == -1) { 224 | perror("sigaction"); 225 | exit(EXIT_FAILURE); 226 | } 227 | 228 | if (csegm==NULL) { 229 | printf ("no stream=.... in config! Nothing to monitor!\n"); 230 | exit (1); 231 | } 232 | int x,y; 233 | for(x=0;x0 && (WIFEXITED(status)||WIFSIGNALED(status))) cleanchilds(pid); 254 | for(x=0;x 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | 32 | #include "segmenter.h" 33 | //Type of options detected 34 | typedef enum { 35 | INPUT_FILE, OUTPUT_FILE, OUTPUT_DIR, OUTPUT_BASE_NAME,SEGMENT_LENGTH, LIST_LENGTH, 36 | 37 | QUIET,PERSISTENT, VERSION, PRINT_USAGE, 38 | 39 | NO_MORE_OPTIONS,INVALID 40 | 41 | } inputOption; 42 | 43 | void printBanner() { 44 | static int bannerPrinted= 1; 45 | if (bannerPrinted==1) { 46 | bannerPrinted++; 47 | fprintf(stderr,"Segmenter for HTTP Live Streaming v" PROGRAM_VERSION "\n"); 48 | fprintf(stderr,"part of HTTP-Live-Video-Stream-Segmenter-and-Distributor @ github\n\n"); 49 | } 50 | } 51 | void printUsage_short(int shortonly){ 52 | printBanner(); 53 | if (shortonly) fprintf(stderr, "\n"); 54 | fprintf(stderr, "Usage: segmenter -i infile [-d baseDir] [-f baseFileName] [-o playListFile.m3u8] [-l ] [-m maxlist] [-q] [-h] \n"); 55 | if (shortonly) fprintf(stderr, " segmenter -h for full help\n"); 56 | } 57 | 58 | void printUsage() { 59 | printUsage_short(0); 60 | fprintf(stderr, "\nOptions (you can use -- or - for short option prefix e.g. -i == --i): \n" 61 | "-i \t\tInput file. Required. - is translated to 'pipe:' before handling to libav \n" 62 | "-o \t\tPlaylist file to create. Default is .m3u8 \n" 63 | "-d \t\tThe base directory for files. Default is '.'\n" 64 | "-f \tSegment files will be named -#. Default is \n" 65 | "-l \tThe length of each segment. Default is 5\n" 66 | "-m \tThe length of produced list. Default is - no limit. \"Hard\" limit at %u\n" 67 | "\t\t\t -m a will enable archive mode\n" 68 | "-p \tenable persistent reconnect to source\n" 69 | "--version\t\tPrint version details and exit.\n" 70 | "-q,--quiet\t\tTry to be more quiet.\n" 71 | "-h,--help\t\tPrint this info.\n" 72 | "\n\n",MAX_SEGMENTS); 73 | } 74 | 75 | 76 | inputOption getNextOption(int argc, const char * argv[], char * option, int *optioni) { 77 | static int optionIndex = 1; 78 | 79 | if (optionIndex >= argc) 80 | return NO_MORE_OPTIONS; 81 | int hasnext=(optionIndex 2.\n"); 199 | return INVALID; 200 | } 201 | if (optioni) *optioni=a; 202 | optionIndex++; 203 | return SEGMENT_LENGTH; 204 | } 205 | 206 | if (strcmp(argv[optionIndex],"-m")==0 || strcmp(argv[optionIndex],"--m")==0) { 207 | if (!hasnext){ 208 | fprintf(stderr,"ERROR: length must be given after -m\n"); 209 | return INVALID; 210 | } 211 | strncpy(option, argv[++optionIndex], MAX_FILENAME_LENGTH ); 212 | option[MAX_FILENAME_LENGTH - 1] = 0; 213 | 214 | if (strcmp(option,"a")==0 || strcmp(option,"archive")==0) { 215 | optionIndex++; 216 | if (optioni) *optioni=-1; 217 | return LIST_LENGTH; 218 | } 219 | 220 | int a = strtol(option, NULL, 10); 221 | if (a == 0 && errno != 0) { 222 | fprintf(stderr,"ERROR: list length must be an integer.\n"); 223 | return INVALID; 224 | } 225 | 226 | if (a <= 3) { 227 | fprintf(stderr, "ERROR: list list must be > 3.\n"); 228 | return INVALID; 229 | } 230 | if (optioni) *optioni=a; 231 | optionIndex++; 232 | return LIST_LENGTH; 233 | } 234 | 235 | if (strcmp(argv[optionIndex],"--version")==0 ) { 236 | optionIndex++; 237 | return VERSION; 238 | } 239 | 240 | if (strcmp(argv[optionIndex],"-h")==0 || strcmp(argv[optionIndex],"--help")==0) { 241 | optionIndex++; 242 | return PRINT_USAGE; 243 | } 244 | 245 | fprintf(stderr, "ERROR: Unknown option %s\n",argv[optionIndex]); 246 | 247 | return INVALID; 248 | } 249 | 250 | 251 | #define INPUT_FILE_INDEX 0 252 | #define OUTPUT_FILE_INDEX 1 253 | #define OUTPUT_BASE_NAME_INDEX 2 254 | 255 | //assumes that the pointers have allocated memory 256 | 257 | int parseCommandLine( 258 | int argc, const char * argv[], 259 | 260 | char * inputFile, char * outputFile, char * baseDir, char * baseName, char * baseExtension, int * segmentLength, int *listlen, 261 | 262 | int * quiet, int * version, int * usage, int *persistent 263 | 264 | ) { 265 | printBanner(); 266 | int requiredOptions[3] = {0, 0, 0}; 267 | 268 | inputOption result; 269 | char option[MAX_FILENAME_LENGTH]; 270 | int optioni; 271 | //default video and audio output 272 | *quiet = 0; 273 | *version = 0; 274 | *usage = 0; 275 | *persistent=0; 276 | *segmentLength=5; 277 | strncpy(baseExtension, ".ts",MAXT_EXT_LENGTH); 278 | baseDir[0]='.';baseDir[1]=0; 279 | *listlen=-1; 280 | 281 | while (1) { 282 | result = getNextOption(argc, argv, option,&optioni); 283 | switch (result) { 284 | case INPUT_FILE: 285 | strncpy(inputFile, option, MAX_FILENAME_LENGTH); 286 | requiredOptions[INPUT_FILE_INDEX] = 1; 287 | break; 288 | case OUTPUT_FILE: 289 | strncpy(outputFile, option, MAX_FILENAME_LENGTH); 290 | requiredOptions[OUTPUT_FILE_INDEX] = 1; 291 | break; 292 | case OUTPUT_DIR: 293 | strncpy(baseDir, option, MAX_FILENAME_LENGTH); 294 | break; 295 | case OUTPUT_BASE_NAME: 296 | strncpy(baseName, option, MAX_FILENAME_LENGTH); 297 | requiredOptions[OUTPUT_BASE_NAME_INDEX] = 1; 298 | break; 299 | case SEGMENT_LENGTH: 300 | *segmentLength = optioni; 301 | case LIST_LENGTH: 302 | *listlen = optioni; 303 | case QUIET: 304 | *quiet = 1; 305 | break; 306 | case PERSISTENT: 307 | *persistent = 1; 308 | break; 309 | case VERSION: 310 | *version = 1; 311 | break; 312 | case PRINT_USAGE: 313 | *usage = 1; 314 | break; 315 | case NO_MORE_OPTIONS: 316 | { 317 | if (argc==1) *usage=1; 318 | if (*version == 1 || *usage == 1) 319 | return 0; 320 | 321 | int missing = 0; 322 | 323 | if (requiredOptions[INPUT_FILE_INDEX] == 0) { 324 | fprintf(stderr, "ERROR: Missing required option --i for input file.\n"); 325 | missing = 1; 326 | return -1; 327 | } 328 | if (requiredOptions[OUTPUT_FILE_INDEX] == 0) { 329 | char *from=inputFile,*to=outputFile,*lastd=NULL,cc,*rebase=NULL; 330 | while ((cc=*from)) { 331 | from++; 332 | *to=cc; 333 | if (cc=='.') lastd=to; 334 | to++; 335 | if (cc=='/') rebase=to; 336 | } 337 | *to=0; 338 | if (!lastd) lastd=to; 339 | if (lastd-outputFile 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include 37 | #include 38 | 39 | #include "segmenter.h" 40 | #include "libavformat/avformat.h" 41 | #include "libavcodec/avcodec.h" 42 | 43 | #if LIBAVCODEC_VERSION_MICRO<100 44 | #define CODEC_ID_MP3 AV_CODEC_ID_MP3 45 | #define CODEC_ID_AC3 AV_CODEC_ID_AC3 46 | #endif 47 | 48 | static AVStream *add_output_stream(AVFormatContext *output_format_context, AVStream *input_stream) { 49 | AVCodecContext *input_codec_context; 50 | AVCodecContext *output_codec_context; 51 | AVStream *output_stream; 52 | 53 | output_stream = avformat_new_stream(output_format_context, 0); 54 | if (!output_stream) { 55 | fprintf(stderr, "Could not allocate stream\n"); 56 | exit(1); 57 | } 58 | 59 | input_codec_context = input_stream->codec; 60 | output_codec_context = output_stream->codec; 61 | 62 | output_codec_context->codec_id = input_codec_context->codec_id; 63 | output_codec_context->codec_type = input_codec_context->codec_type; 64 | output_codec_context->codec_tag = input_codec_context->codec_tag; 65 | output_codec_context->bit_rate = input_codec_context->bit_rate; 66 | output_codec_context->extradata = input_codec_context->extradata; 67 | output_codec_context->extradata_size = input_codec_context->extradata_size; 68 | 69 | if (av_q2d(input_codec_context->time_base) * input_codec_context->ticks_per_frame > av_q2d(input_stream->time_base) && av_q2d(input_stream->time_base) < 1.0 / 1000) { 70 | output_codec_context->time_base = input_codec_context->time_base; 71 | output_codec_context->time_base.num *= input_codec_context->ticks_per_frame; 72 | } else { 73 | output_codec_context->time_base = input_stream->time_base; 74 | } 75 | output_stream->time_base=output_codec_context->time_base; 76 | 77 | 78 | switch (input_codec_context->codec_type) { 79 | case AVMEDIA_TYPE_AUDIO: 80 | output_codec_context->channel_layout = input_codec_context->channel_layout; 81 | output_codec_context->sample_rate = input_codec_context->sample_rate; 82 | output_codec_context->channels = input_codec_context->channels; 83 | output_codec_context->frame_size = input_codec_context->frame_size; 84 | if ((input_codec_context->block_align == 1 && input_codec_context->codec_id == AV_CODEC_ID_MP3) || input_codec_context->codec_id == AV_CODEC_ID_AC3) { 85 | output_codec_context->block_align = 0; 86 | } else { 87 | output_codec_context->block_align = input_codec_context->block_align; 88 | } 89 | break; 90 | case AVMEDIA_TYPE_VIDEO: 91 | output_codec_context->pix_fmt = input_codec_context->pix_fmt; 92 | output_codec_context->width = input_codec_context->width; 93 | output_codec_context->height = input_codec_context->height; 94 | output_codec_context->has_b_frames = input_codec_context->has_b_frames; 95 | 96 | break; 97 | default: 98 | break; 99 | } 100 | 101 | return output_stream; 102 | } 103 | 104 | int write_index_file( 105 | const char *index, const char *tmp_index, 106 | unsigned int planned_segment_duration,unsigned int numsegments, unsigned int *actual_segment_duration, unsigned int segment_number_offset, 107 | const char *output_prefix, const char *output_file_extension,int islast 108 | ) { 109 | if (numsegments<1) return 0; 110 | FILE *tmp_index_fp; 111 | 112 | unsigned int i; 113 | 114 | tmp_index_fp = fopen(tmp_index, "w"); 115 | if (!tmp_index_fp) { 116 | fprintf(stderr, "Could not open temporary m3u8 index file (%s), no index file will be created\n", tmp_index); 117 | return -1; 118 | } 119 | 120 | 121 | unsigned int maxDuration = actual_segment_duration[0]; 122 | 123 | for (i = 1; i maxDuration) maxDuration = actual_segment_duration[i]; 124 | 125 | 126 | 127 | fprintf(tmp_index_fp, "#EXTM3U\n#EXT-X-MEDIA-SEQUENCE:%u\n#EXT-X-TARGETDURATION:%u\n", segment_number_offset,maxDuration); 128 | 129 | 130 | for (i = 0; i tm_year+=1900; 174 | dst->tm_mon++; 175 | if (dst->tm_wday==0) dst->tm_wday=7; 176 | } 177 | 178 | void fillofn (){ 179 | 180 | snprintf(currentOutputDirName, MAX_FILENAME_LENGTH, "%s/%d-%02d-%02d", baseDirName, start_time.tm_year,start_time.tm_mon,start_time.tm_mday); 181 | 182 | forcedir(currentOutputDirName); 183 | snprintf(currentOutputFileName,MAX_FILENAME_LENGTH, "%s/%s_%d-%02d-%02d_%02d.%02d.%02d_current%s",currentOutputDirName,baseFileName,start_time.tm_year,start_time.tm_mon,start_time.tm_mday,start_time.tm_hour,start_time.tm_min,start_time.tm_sec,baseFileExtension); 184 | 185 | } 186 | 187 | void fixofn(){ 188 | FNHOLDER(currentOutputFinalName); 189 | snprintf(currentOutputFinalName,MAX_FILENAME_LENGTH, 190 | "%s/%s_%d-%02d-%02d_%02d.%02d.%02d_to_%d-%02d-%02d_%02d.%02d.%02d%s", 191 | currentOutputDirName,baseFileName, 192 | start_time.tm_year,start_time.tm_mon,start_time.tm_mday,start_time.tm_hour,start_time.tm_min,start_time.tm_sec, 193 | end_time.tm_year,end_time.tm_mon,end_time.tm_mday,end_time.tm_hour,end_time.tm_min,end_time.tm_sec, 194 | baseFileExtension 195 | ); 196 | printf ("rename: %s to %s\n",currentOutputFileName,currentOutputFinalName); 197 | if (rename(currentOutputFileName,currentOutputFinalName)) { 198 | perror("rename"); 199 | exit(-1); 200 | } 201 | 202 | start_time=end_time; 203 | fillofn (); 204 | } 205 | 206 | int main(int argc, const char *argv[]) { 207 | //input parameters 208 | FNHOLDER(inputFilename); 209 | FNHOLDER(playlistFilename); 210 | baseDirName [MAX_FILENAME_LENGTH ]=0; 211 | baseFileName[MAX_FILENAME_LENGTH ]=0; 212 | currentOutputDirName[MAX_FILENAME_LENGTH ]=0; 213 | baseFileExtension[MAX_FILENAME_LENGTH ]=0; 214 | 215 | baseFileExtension[MAXT_EXT_LENGTH]=0; //either "ts", "aac" or "mp3" 216 | int segmentLength, quiet, version,usage; 217 | 218 | 219 | 220 | 221 | FNHOLDER(tempPlaylistName); 222 | 223 | 224 | //these are used to determine the exact length of the current segment 225 | double segment_start_time = 0; 226 | unsigned int actual_segment_durations[MAX_SEGMENTS+1]; 227 | double packet_time = 0; 228 | 229 | unsigned int output_index = 1; 230 | AVOutputFormat *ofmt=NULL; 231 | AVFormatContext *ic = NULL; 232 | AVFormatContext *oc; 233 | AVStream *in_video_st = NULL; 234 | AVStream *in_audio_st = NULL; 235 | AVStream *out_video_st = NULL; 236 | AVStream *out_audio_st = NULL; 237 | AVCodec *codec; 238 | 239 | unsigned int num_segments = 0; 240 | 241 | int decode_done; 242 | int ret; 243 | int i; 244 | int listlen; 245 | int listofs=1; 246 | int persist=0; 247 | 248 | 249 | if ( parseCommandLine(argc, argv,inputFilename, playlistFilename, baseDirName, baseFileName, baseFileExtension, &segmentLength, &listlen, &quiet, &version,&usage,&persist) != 0) 250 | return 0; 251 | 252 | if (usage) printUsage(); 253 | if (version) ffmpeg_version(); 254 | if (version || usage) return 0; 255 | 256 | //fprintf(stderr, "Options parsed: inputFilename:%s playlistFilename:%s baseDirName:%s baseFileName:%s baseFileExtension:%s segmentLength:%d\n",inputFilename,playlistFilename,baseDirName,baseFileName,baseFileExtension,segmentLength ); 257 | 258 | 259 | if (listlen>0){ 260 | snprintf(tempPlaylistName, MAX_FILENAME_LENGTH, "%s/%s", baseDirName, playlistFilename); 261 | strncpy(playlistFilename, tempPlaylistName, MAX_FILENAME_LENGTH); 262 | snprintf(tempPlaylistName, MAX_FILENAME_LENGTH, "%s.tmp", playlistFilename); 263 | } 264 | 265 | //if (!quiet) av_log_set_level(AV_LOG_DEBUG); 266 | 267 | av_register_all(); 268 | avformat_network_init(); //just to be safe with later version and be able to handle all kind of input urls 269 | 270 | while(1) { 271 | ret = avformat_open_input(&ic, inputFilename, NULL, NULL); 272 | if (ret != 0) { 273 | if (persist) { 274 | sleep(1); 275 | continue; 276 | } 277 | fprintf(stderr, "Could not open input file %s. Error %d.\n", inputFilename, ret); 278 | exit(1); 279 | } 280 | 281 | if (avformat_find_stream_info(ic, NULL) < 0) { 282 | fprintf(stderr, "Could not read stream information.\n"); 283 | if (persist){ 284 | avformat_close_input(&ic); 285 | sleep(1); 286 | continue; 287 | } 288 | exit(1); 289 | } 290 | 291 | oc = avformat_alloc_context(); 292 | if (!oc) { 293 | fprintf(stderr, "Could not allocate output context."); 294 | if (persist){ 295 | avformat_close_input(&ic); 296 | sleep(1); 297 | continue; 298 | } 299 | exit(1); 300 | } 301 | 302 | int in_video_index = -1; 303 | int in_audio_index = -1; 304 | int out_video_index = -1; 305 | int out_audio_index = -1; 306 | 307 | for (i = 0; i < ic->nb_streams; i++) { 308 | switch (ic->streams[i]->codec->codec_type) { 309 | case AVMEDIA_TYPE_VIDEO: 310 | if (!out_video_st) { 311 | in_video_st=ic->streams[i]; 312 | in_video_index = i; 313 | in_video_st->discard = AVDISCARD_NONE; 314 | out_video_st = add_output_stream(oc, in_video_st); 315 | out_video_index=out_video_st->index; 316 | } 317 | break; 318 | case AVMEDIA_TYPE_AUDIO: 319 | if (!out_audio_st) { 320 | in_audio_st=ic->streams[i]; 321 | in_audio_index = i; 322 | in_audio_st->discard = AVDISCARD_NONE; 323 | out_audio_st = add_output_stream(oc, in_audio_st); 324 | out_audio_index=out_audio_st->index; 325 | } 326 | break; 327 | default: 328 | ic->streams[i]->discard = AVDISCARD_ALL; 329 | break; 330 | } 331 | } 332 | 333 | if (in_video_index == -1) { 334 | fprintf(stderr, "Source stream must have video component.\n"); 335 | if (persist){ 336 | avformat_close_input(&ic); 337 | avformat_free_context(oc); 338 | sleep(1); 339 | continue; 340 | } 341 | exit(1); 342 | } 343 | 344 | 345 | if (!ofmt) ofmt = av_guess_format("mpegts", NULL, NULL); 346 | if (!ofmt) { 347 | fprintf(stderr, "Could not find MPEG-TS muxer.\n"); 348 | exit(1); 349 | } 350 | 351 | oc->oformat = ofmt; 352 | 353 | if (oc->oformat->flags & AVFMT_GLOBALHEADER) oc->flags |= CODEC_FLAG_GLOBAL_HEADER; 354 | 355 | av_dump_format(oc, 0, baseFileName, 1); 356 | 357 | 358 | codec = avcodec_find_decoder(in_video_st->codec->codec_id); 359 | if (!codec) { 360 | fprintf(stderr, "Could not find video decoder, key frames will not be honored.\n"); 361 | } 362 | ret = avcodec_open2(in_video_st->codec, codec, NULL); 363 | if (ret < 0) { 364 | fprintf(stderr, "Could not open video decoder, key frames will not be honored.\n"); 365 | } 366 | 367 | if (listlen>0){ 368 | snprintf(currentOutputFileName, MAX_FILENAME_LENGTH, "%s/%s-%u%s", baseDirName, baseFileName, output_index, baseFileExtension); 369 | } else { //archive mode 370 | localtime_r_ex(&start_time); 371 | fillofn(); 372 | } 373 | 374 | if (avio_open(&oc->pb, currentOutputFileName,AVIO_FLAG_WRITE) < 0) { 375 | fprintf(stderr, "Could not open '%s'.\n", currentOutputFileName); 376 | exit(1); 377 | } else if (!quiet) fprintf(stderr, "Starting segment '%s'\n", currentOutputFileName); 378 | 379 | int r = avformat_write_header(oc, NULL); 380 | if (r) { 381 | fprintf(stderr, "Could not write mpegts header to first output file.\n"); 382 | debugReturnCode(r); 383 | exit(1); 384 | } 385 | 386 | 387 | int waitfirstpacket=1; 388 | time_t first_frame_sec=time(NULL); 389 | 390 | int iskeyframe=0; 391 | double vid_pts2time=(double)in_video_st->time_base.num / in_video_st->time_base.den; 392 | 393 | //double aud_pts2time=0; 394 | //if (in_audio_st) aud_pts2time=(double)in_audio_st->time_base.num / in_audio_st->time_base.den; 395 | 396 | 397 | double prev_packet_time=0; 398 | do { 399 | AVPacket packet; 400 | 401 | decode_done = av_read_frame(ic, &packet); 402 | 403 | if (decode_done < 0) { 404 | break; 405 | } 406 | 407 | //a potential memory leak: 408 | // if (av_dup_packet(&packet) < 0) { 409 | // fprintf(stderr, "Could not duplicate packet."); 410 | // av_packet_unref(&packet); 411 | // break; 412 | // } 413 | 414 | 415 | //get the most recent packet time 416 | //this time is used when the time for the final segment is printed. It may not be on the edge of 417 | //of a keyframe! 418 | if (packet.stream_index == in_video_index) { 419 | packet.stream_index = out_video_index; 420 | packet_time = (double) packet.pts * vid_pts2time; 421 | iskeyframe=packet.flags & AV_PKT_FLAG_KEY; 422 | if (iskeyframe && waitfirstpacket) { 423 | waitfirstpacket=0; 424 | prev_packet_time=packet_time; 425 | segment_start_time=packet_time; 426 | first_frame_sec=time(NULL); 427 | } 428 | } else if (packet.stream_index == in_audio_index){ 429 | packet.stream_index = out_audio_index; 430 | iskeyframe=0; 431 | } else { 432 | //how this got here?! 433 | av_packet_unref(&packet); 434 | continue; 435 | } 436 | 437 | 438 | if (waitfirstpacket) { 439 | av_packet_unref(&packet); 440 | continue; 441 | } 442 | 443 | //start looking for segment splits for videos one half second before segment duration expires. This is because the 444 | //segments are split on key frames so we cannot expect all segments to be split exactly equally. 445 | if (iskeyframe && ((packet_time - segment_start_time) >= (segmentLength - 0.25)) && (time(NULL)!=first_frame_sec)) { //a keyframe near or past segmentLength -> SPLIT 446 | avio_flush(oc->pb); 447 | avio_close(oc->pb); 448 | if (listlen>0){ 449 | actual_segment_durations[num_segments] = (unsigned int) rint(prev_packet_time - segment_start_time); 450 | num_segments++; 451 | if (num_segments>listlen) { //move list to exclude last: 452 | snprintf(currentOutputFileName, MAX_FILENAME_LENGTH, "%s/%s-%u%s", baseDirName, baseFileName, listofs, baseFileExtension); 453 | unlink (currentOutputFileName); 454 | listofs++; num_segments--; 455 | memmove(actual_segment_durations,actual_segment_durations+1,num_segments*sizeof(actual_segment_durations[0])); 456 | 457 | } 458 | write_index_file(playlistFilename, tempPlaylistName, segmentLength, num_segments,actual_segment_durations, listofs, baseFileName, baseFileExtension, (num_segments>=MAX_SEGMENTS)); 459 | 460 | if (num_segments==MAX_SEGMENTS) { 461 | fprintf(stderr, "Reached \"hard\" max segment number %u. If this is not live stream increase segment duration. If live segmenting set max list lenth (-m ...)\n", MAX_SEGMENTS); 462 | break; 463 | } 464 | output_index++; 465 | snprintf(currentOutputFileName, MAX_FILENAME_LENGTH, "%s/%s-%u%s", baseDirName, baseFileName, output_index, baseFileExtension); 466 | } else { //archive mode: 467 | localtime_r_ex(&end_time); 468 | fixofn(); 469 | } 470 | 471 | if (avio_open(&oc->pb, currentOutputFileName, AVIO_FLAG_WRITE) < 0) { 472 | fprintf(stderr, "Could not open '%s'\n", currentOutputFileName); 473 | break; 474 | } else if (!quiet) fprintf(stderr, "Starting segment '%s'\n", currentOutputFileName); 475 | fflush(stderr); 476 | segment_start_time = packet_time; 477 | first_frame_sec=time(NULL); 478 | } 479 | if (packet.stream_index == out_video_index) prev_packet_time=packet_time; 480 | 481 | ret = av_write_frame(oc, &packet); 482 | 483 | if (ret < 0) { 484 | fprintf(stderr, "Warning: Could not write frame of stream.\n"); 485 | } else if (ret > 0) { 486 | fprintf(stderr, "End of stream requested.\n"); 487 | av_packet_unref(&packet); 488 | break; 489 | } 490 | 491 | av_packet_unref(&packet); 492 | } while (!decode_done); 493 | 494 | if (in_video_st->codec->codec !=NULL) avcodec_close(in_video_st->codec); 495 | if (num_segmentspb); 498 | av_write_trailer(oc); 499 | 500 | for (i = 0; i < oc->nb_streams; i++) { 501 | av_freep(&oc->streams[i]->codec); 502 | av_freep(&oc->streams[i]); 503 | } 504 | 505 | avio_close(oc->pb); 506 | av_free(oc); 507 | 508 | if (num_segments>0){ 509 | actual_segment_durations[num_segments] = (unsigned int) rint(packet_time - segment_start_time); 510 | if (actual_segment_durations[num_segments] == 0) actual_segment_durations[num_segments] = 1; 511 | num_segments++; 512 | write_index_file(playlistFilename, tempPlaylistName, segmentLength, num_segments,actual_segment_durations, listofs, baseFileName, baseFileExtension, 1); 513 | } else { //archive mode 514 | localtime_r_ex(&end_time); 515 | fixofn(); 516 | } 517 | } 518 | // struct stat st; 519 | // stat(currentOutputFileName, &st); 520 | // output_bytes += st.st_size; 521 | avformat_close_input(&ic); 522 | break; 523 | } 524 | 525 | 526 | 527 | return 0; 528 | } 529 | --------------------------------------------------------------------------------