├── ASCIIPlay.gif ├── controller.h ├── .gitignore ├── CMakeLists.txt ├── renderer.h ├── helpers.h ├── controller.c ├── main.h ├── constants.h ├── helpers.c ├── README.md ├── renderer.c └── main.c /ASCIIPlay.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/aidancrowther/ASCIIPlay/HEAD/ASCIIPlay.gif -------------------------------------------------------------------------------- /controller.h: -------------------------------------------------------------------------------- 1 | #ifndef CONTROLLER 2 | #define CONTROLLER 3 | 4 | #include "constants.h" 5 | 6 | void *inputHandler(); 7 | 8 | #endif -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | CMakeCache.txt 2 | CMakeFiles 3 | Makefile 4 | cmake_install.cmake 5 | *.mp4 6 | shrek 7 | *.o 8 | Lib/ 9 | *.srt 10 | *.txt 11 | telnetflix 12 | temp/ 13 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project( telnetflix ) 3 | include_directories( Lib ) 4 | add_executable( telnetflix main.c helpers.c Lib/ascii_art.c renderer.c controller.c ) 5 | target_link_libraries( telnetflix -I ./include -L./lib -lavformat -lavcodec -lswscale -lavutil -lswresample -lz -lm -lpthread -lcurses) -------------------------------------------------------------------------------- /renderer.h: -------------------------------------------------------------------------------- 1 | #ifndef RENDERER 2 | #define RENDERER 3 | 4 | #include "constants.h" 5 | 6 | // Render the frame to ASCII 7 | void renderFrame(uint8_t *img, int width, int height); 8 | 9 | // Strip unwanted characters from strings and center them 10 | void prepSubs(char *str); 11 | 12 | // Superimpose subtitles over the generated ASCII 13 | void generateSubs(char **frame); 14 | 15 | // Resize a given frame by the specified scale factor 16 | void resizeFrame(uint8_t *img, int width, int height); 17 | 18 | #endif -------------------------------------------------------------------------------- /helpers.h: -------------------------------------------------------------------------------- 1 | #ifndef HELPERS 2 | #define HELPERS 3 | 4 | #include "constants.h" 5 | 6 | // Convert an AVFrame to a B/W image 7 | uint8_t* grayscale(AVFrame *pFrame, int width, int height); 8 | 9 | // Convert time codes to seconds 10 | double decodeTime(char* time, int last); 11 | 12 | // Resize a given frame by the specified scale factor 13 | void resizeFrame(uint8_t *img, int width, int height); 14 | 15 | void betterScaler(uint8_t *img, int width, int height); 16 | 17 | // Compare two timeval structs to determine difference 18 | int timeval_subtract (struct timeval *result, struct timeval *x, struct timeval *y); 19 | 20 | int getNiceFramerate(double framerate); 21 | 22 | #endif -------------------------------------------------------------------------------- /controller.c: -------------------------------------------------------------------------------- 1 | #include "controller.h" 2 | 3 | void *inputHandler(){ 4 | 5 | // Listen to user input for commands 6 | while(!controls.cease_execution){ 7 | 8 | char input = getchar(); 9 | 10 | switch(input){ 11 | case (' '): 12 | controls.pause = !controls.pause; 13 | sleep(1); 14 | break; 15 | 16 | case ('f'): 17 | controls.fast_forward = !controls.fast_forward; 18 | sleep(1); 19 | break; 20 | 21 | case (27): 22 | controls.cease_execution = true; 23 | break; 24 | } 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /main.h: -------------------------------------------------------------------------------- 1 | #ifndef MAIN 2 | #define MAIN 3 | 4 | #include "constants.h" 5 | #include "helpers.h" 6 | #include "renderer.h" 7 | #include "controller.h" 8 | 9 | // Open up the file data stream and parse frames 10 | void openStream(struct vStreamArgs* args); 11 | 12 | // Load array of subtitle time codes 13 | void loadSubs(); 14 | 15 | // Write new frame data to the tail of the buffer queue 16 | void writeBuffer(struct vBuffer **buffer, AVFrame *frame); 17 | 18 | // Return frame buffer at the head of our queue 19 | struct vBuffer* readBuffer(struct vBuffer **buffer); 20 | 21 | // Run continuously in the background rendering frames when needed 22 | void *renderingEngine(struct vBuffer *buffer); 23 | 24 | // Spawn all neccessary threads and manage them 25 | int main(int argc, char *argv[]); 26 | 27 | #endif -------------------------------------------------------------------------------- /constants.h: -------------------------------------------------------------------------------- 1 | #ifndef CONSTANTS 2 | #define CONSTANTS 3 | 4 | #define ALIGNMENT 32 5 | #define MAX_BUFFER 100 6 | #define MAX_CHAR 50 7 | #define NUM_ROWS 2 8 | #define NUM_THREADS 3 9 | #define NUM_CHANNELS 3 10 | #define TIME_CODE_LENGTH 11 11 | #define MATCH_EXPR "[0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9] --> [0-9][0-9]:[0-9][0-9]:[0-9][0-9],[0-9][0-9][0-9]" 12 | #define CHAR_X 8 13 | #define CHAR_Y 8 14 | #define R 0 15 | #define G 1 16 | #define B 2 17 | 18 | #include "Lib/ascii_art.h" 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | 34 | 35 | struct videoStream { 36 | double fps; 37 | double srcFps; 38 | int width; 39 | int scr_width; 40 | int height; 41 | int scr_height; 42 | int scale; 43 | double time; 44 | double realTime; 45 | int frameCount; 46 | char* subFile; 47 | double* subTimes; 48 | char subs[2][MAX_CHAR]; 49 | int subPos; 50 | int fpPos; 51 | atomic_int bufferLength; 52 | pthread_mutex_t mutex; 53 | atomic_bool bufferEnd; 54 | }; 55 | 56 | struct vBuffer { 57 | uint8_t *frame; 58 | struct vBuffer *next; 59 | double timestamp; 60 | }; 61 | 62 | struct vStreamArgs { 63 | char *file; 64 | struct vBuffer *buffer; 65 | }; 66 | 67 | struct playback { 68 | atomic_bool pause; 69 | atomic_bool fast_forward; 70 | atomic_bool cease_execution; 71 | }; 72 | 73 | extern struct videoStream vStream; 74 | extern struct timespec wait; 75 | extern struct playback controls; 76 | 77 | extern FILE* subFile; 78 | extern regex_t matchTime; 79 | extern int debug; 80 | extern int skew; 81 | 82 | #endif 83 | -------------------------------------------------------------------------------- /helpers.c: -------------------------------------------------------------------------------- 1 | #include "helpers.h" 2 | 3 | // Convert an AVFrame to a B/W image 4 | uint8_t* grayscale(AVFrame *pFrame, int width, int height){ 5 | uint8_t *img = malloc(width*height * sizeof(uint8_t*)); 6 | uint8_t pixel[NUM_CHANNELS]; 7 | 8 | // Iterate over the frame buffer 9 | for(int y = 0; y < height; y++){ 10 | for(int x = 0; x < width; x++){ 11 | /* 12 | for(int channel=0; channel < NUM_CHANNELS; channel++){ 13 | // Capture pixel data for each channel 14 | pixel[channel] = *(pFrame->data[0]+y*(pFrame->linesize[0])+(x/2)+channel); 15 | } 16 | */ 17 | // Convert the three channels to a grayscale representation 18 | *(img+(x)+y*width) = *(pFrame->data[0]+y*(pFrame->linesize[0])+(x/2)); 19 | } 20 | } 21 | 22 | return img; 23 | } 24 | 25 | void resizeFrame(uint8_t *img, int width, int height){ 26 | /* 27 | // The destination dimensions of our array 28 | int new_width = width - vStream.scale_x*CHAR_X; 29 | int new_height = height - vStream.scale_y*CHAR_Y; 30 | 31 | // Determine the interval at which we need to drop lines 32 | int skip_x = width / (width - new_width); 33 | int skip_y = height / (height - new_height); 34 | 35 | int loc = 0; 36 | 37 | // Scale y axis to the new dimensions 38 | for(int j=0; j 0){ 72 | loc++; 73 | if(count++ >= skipped) count = 0; 74 | continue; 75 | } 76 | count++; 77 | for(int i=0; i 0){ 88 | loc++; 89 | if(count++ >= skipped) count = 0; 90 | continue; 91 | } 92 | count++; 93 | *(img+(i-loc)) = *(img+(i)); 94 | } 95 | } 96 | 97 | int getNiceFramerate(double framerate){ 98 | int multiplier = 1; 99 | while(ceil(framerate*multiplier) != floor(framerate*multiplier)) multiplier *= 10; 100 | return multiplier; 101 | } 102 | 103 | // Convert time codes to seconds 104 | double decodeTime(char* time, int last){ 105 | 106 | // Determine offset based on which timecode we want 107 | int offset = 0; 108 | if (last) offset = 17; 109 | 110 | // Multiplication factors 111 | double factor[9] = {36000, 3600, 600, 60, 10, 1, 0.1, 0.01, 0.001}; 112 | int index = 0; 113 | double result = 0.0; 114 | 115 | // Iterate over our timecode converting to seconds 116 | for(int i=offset; i<=offset+TIME_CODE_LENGTH; i++){ 117 | if(*(time+i) == ':' || *(time+i) == ',') continue; 118 | result += (((int) *(time+i)) - '0') * factor[index++]; 119 | } 120 | 121 | return result; 122 | 123 | } 124 | 125 | // Compare two timeval structs to determine difference 126 | int timeval_subtract (result, x, y) 127 | struct timeval *result, *x, *y; 128 | { 129 | /* Perform the carry for the later subtraction by updating y. */ 130 | if (x->tv_usec < y->tv_usec) { 131 | int nsec = (y->tv_usec - x->tv_usec) / 1000000 + 1; 132 | y->tv_usec -= 1000000 * nsec; 133 | y->tv_sec += nsec; 134 | } 135 | if (x->tv_usec - y->tv_usec > 1000000) { 136 | int nsec = (y->tv_usec - x->tv_usec) / 1000000; 137 | y->tv_usec += 1000000 * nsec; 138 | y->tv_sec -= nsec; 139 | } 140 | 141 | result->tv_sec = x->tv_sec - y->tv_sec; 142 | result->tv_usec = x->tv_usec - y->tv_usec; 143 | 144 | /* Return 1 if result is negative. */ 145 | return x->tv_sec < y->tv_sec; 146 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ASCIIPlay 2 | 3 | ASCIIPlay is a video player designed for the purpouse of rendering to ASCII any video file given to it, inspired by towel.blinkenlights.nl. The reasoning for my making my own ASCII video player is due to the lack of an existing ASCII renderer with support for subtitle or audio playback. Thus I hope for ASCIIPlay to develop to fill this niche. 4 | 5 | A server is currently publicly accessible to view an example of this project, and can be accessed using: `ssh anonymous@telnetflix.com -p 20` 6 | 7 | ![](ASCIIPlay.gif) 8 | 9 | ## Goals 10 | 11 | My main goal for ASCIIPlay is to develop a fully functional video player with a variety of features, while also practicing my C development and trying features I haven't previously used much. To this regard, I have definitely learned much more about data manipulation and threading in C programming, and I hope to expand into more practice as the project moves forward. 12 | 13 | ## Status 14 | 15 | ASCIIPlay is currently stable, with many features being planned and added frequently. 16 | 17 | Currently Supports: 18 | 19 | - Reading in a variety of video formats such ash mp4, mkv, ... 20 | - Subtitle rendering using SRT formatted subtitles 21 | - Frame limiting for lower end systems or situations where lower bitrate is necessary 22 | - Black and White ASCII rendering only at this time 23 | 24 | ## Roadmap 25 | 26 | - [x] Load and read video files 27 | - [x] Render ASCII representation 28 | - [ ] Use custom ASCII renderer to remove closed source dependencies 29 | - [ ] Colour support 30 | - [ ] Audio renderer 31 | - [x] Add subtitle support 32 | - [ ] More subtitle filetypes 33 | - [ ] Alternate subtitle rendering modes 34 | - [x] Performance improvements 35 | - [x] framerate limiting 36 | - [x] Implement frame buffer 37 | - [x] Make use of threading 38 | - [x] Support Resizing 39 | - [ ] Improve Resizing 40 | - [ ] Add control features 41 | - [x] Play/Pause 42 | - [x] Fast Forward (Improve this) 43 | - [ ] Rewind 44 | - [x] Improve error reporting 45 | - [ ] Auto Installer 46 | 47 | ## Install 48 | 49 | Currently ASCIIPlay is in development status with no installation candidate. If you want to use it you will need to get the [listed dependencies](#Dependencies) in order to compile 50 | 51 | Download repository: 52 | 53 | ``` 54 | git clone https://github.com/aidancrowther/ASCIIPlay 55 | ``` 56 | 57 | Navigate to ASCIIPlay folder and prepare Makefile 58 | 59 | ``` 60 | cmake . 61 | ``` 62 | 63 | Make and run the executable 64 | 65 | ``` 66 | make 67 | ./telnetflix -f -s 68 | ``` 69 | 70 | ## Usage 71 | 72 | After getting ASCIIPlay installed the available flags for the executable are: 73 | 74 | ``` 75 | -f Specify an input video file (Required) 76 | -s Specify an input subtitle file (Optional) 77 | -h Display help menu 78 | --slow-mode Allow slower systems time to start reading the input file 79 | --frame-rate Specify a framerate (>1 && < src framerate) to render at 80 | --no-render Disable the renderer output (Don't recommend using this if you want to see anything) 81 | --enable-skew Disable frametime skew correction (Don't use this if you want subs to do anything useful) 82 | --debug Display debug information while rendering 83 | ``` 84 | 85 | ## Problems 86 | 87 | Please feel free to notify me of any issues you encounter, and I will fix them as soon as possible. I am open to any suggestions or requests, and will work to make the program as functional as possible 88 | 89 | ### Dependencies 90 | 91 | primarily: 92 | * ascii\_art.c (glhf with this one, we commented it out for ya) 93 | * ffmpeg 94 | * cmake 95 | * make 96 | * gcc 97 | 98 | but apparently you'll need some indeterminate number of these too: 99 | * build-essential 100 | * pkg-config 101 | * libgtk-3-dev 102 | * libavcodec-dev 103 | * libavformat-dev 104 | * libswscale-dev 105 | * libv4l-dev 106 | * libxvidcore-dev 107 | * libx264-dev 108 | * libjpeg-dev 109 | * libpng-dev 110 | * libtiff-dev 111 | * gfortran 112 | * openexr 113 | * libatlas-base-dev 114 | * python3-dev 115 | * python3-numpy 116 | * libtbb2 117 | * libtbb-dev 118 | * libdc1394-dev 119 | 120 | ``` 121 | sudo apt install ffmpeg cmake make gcc build-essential pkg-config libgtk-2-dev libavcodec-dev libavformat-dev libswscale-dev libv4l-dev libxvidcore-dev libx264-dev libjpeg-dev libpng-dev libtiff-dev gfortran openexr libatlas-base-dev python3-dev python3-numpy libtbb2 libtbb-dev libdc1394-dev 122 | ``` 123 | -------------------------------------------------------------------------------- /renderer.c: -------------------------------------------------------------------------------- 1 | #include "renderer.h" 2 | 3 | void renderFrame(uint8_t *img, int width, int height) { 4 | ascii_render sRender; 5 | 6 | unsigned char *zText; 7 | unsigned int nBytes; 8 | 9 | // Get rendering dimensions 10 | int x = 0, y = 0; 11 | getmaxyx(stdscr, y, x); 12 | 13 | int scr_x = x, scr_y = y; 14 | 15 | // Update our scaling if needed 16 | if(vStream.scr_width == 0 && vStream.scr_height == 0){ 17 | vStream.scr_width = width/CHAR_X; 18 | vStream.scr_height = height/CHAR_Y; 19 | } 20 | 21 | /* 22 | if (scr_x != vStream.scr_width || scr_y != vStream.scr_height){ 23 | vStream.scr_width = scr_x; 24 | vStream.scr_height = scr_y; 25 | 26 | // Clear buffer region 27 | clear(); 28 | 29 | // Determine the minimum x scaling factor 30 | int scale_x = 0; 31 | while((width/CHAR_X) - scale_x > scr_x) scale_x++; 32 | 33 | // Use nice whole integer scaling for now, not terribly robust sadly 34 | while(scale_x != 0 && width%(scale_x*CHAR_X) != 0) scale_x++; 35 | int scale_y = ceil(((double)height * (double)scale_x) / (double) width); 36 | 37 | // This code is slightly better for scaling, however it is too complicated for me to figure out right now 38 | 39 | // Determine the minimum y scaling factor 40 | //int scale_y = 0; 41 | //while((height/CHAR_Y) - scale_y > scr_y) scale_y++; 42 | 43 | // Modify our scaling factors as needed to maintain aspect ratio 44 | //while((double)scale_x/(double)scale_y > (double)vStream.width/(double)vStream.height) scale_y++; 45 | //while((double)scale_y/(double)scale_x > (double)vStream.height/(double)vStream.width) scale_x++; 46 | 47 | vStream.scale_x = scale_x; 48 | vStream.scale_y = scale_y; 49 | } 50 | */ 51 | 52 | if (scr_x != vStream.scr_width || scr_y != vStream.scr_height){ 53 | vStream.scr_width = scr_x; 54 | vStream.scr_height = scr_y; 55 | 56 | // Clear buffer region 57 | clear(); 58 | 59 | int scale_x = 1; 60 | while((scr_x*CHAR_X) < width/scale_x) scale_x++; 61 | 62 | int scale_y = 1; 63 | while((scr_y*CHAR_Y) < height/scale_y) scale_y++; 64 | 65 | FILE *f = fopen("out.txt", "w"); 66 | fprintf(f, "%d %d\n", scale_x, scale_y); 67 | fclose(f); 68 | 69 | vStream.scale = scale_x > scale_y ? scale_x : scale_y; 70 | } 71 | 72 | //If scaling is needed call the scaler 73 | if(vStream.scale > 1){ 74 | //resizeFrame(img, width, height); 75 | betterScaler(img, width, height); 76 | } 77 | 78 | width = width/vStream.scale, height = height/vStream.scale; 79 | 80 | AsciiArtInit(&sRender); 81 | 82 | // Allocate space for ascii frame 83 | nBytes = AsciiArtTextBufSize(&sRender, width, height); 84 | zText = malloc(nBytes); 85 | 86 | // Call ASCII renderer 87 | AsciiArtRender(&sRender, img, &width, &height, zText, 1); 88 | 89 | // Generate subtitles 90 | if(vStream.subFile != NULL) generateSubs((char **)&zText); 91 | 92 | width = width/CHAR_X; 93 | height = height/CHAR_Y; 94 | 95 | // Determine our text offset to center the screen 96 | x = (x-width)/2; 97 | y = (y-height)/2+(y-height)%2; 98 | 99 | int newBytes = nBytes-height; 100 | unsigned char stripped[newBytes]; 101 | 102 | // Strip newline characters from the output stream 103 | int count = 0; 104 | for (int i = 0; i= offset && i < offset+length) *(str+i) = newString[iterator++]; 171 | } 172 | 173 | // Declare the end of our string 174 | *(str+MAX_CHAR) = '\0'; 175 | 176 | } 177 | 178 | // Superimpose subtitles over the generated ASCII 179 | void generateSubs(char **frame){ 180 | 181 | char buffer[MAX_CHAR]; 182 | double start = *(vStream.subTimes+vStream.subPos*3+0); 183 | double end = *(vStream.subTimes+vStream.subPos*3+1); 184 | int currLine = (int) *(vStream.subTimes+vStream.subPos*3+2); 185 | int reading = 0; 186 | 187 | // Initialize the subtitles 188 | if (vStream.subPos == 0){ 189 | if(vStream.fpPos == 0){ 190 | // Iterate over the subtitles until we find what we want 191 | while(fgets(buffer, sizeof(buffer), subFile) != NULL){ 192 | // Copy the two subtitle strings 193 | if (vStream.fpPos == currLine){ 194 | prepSubs(buffer); 195 | strcpy(vStream.subs[0], buffer); 196 | reading = 1; 197 | } else if (reading){ 198 | prepSubs(buffer); 199 | strcpy(vStream.subs[1], buffer); 200 | vStream.fpPos++; 201 | reading = 0; 202 | break; 203 | } 204 | vStream.fpPos++; 205 | } 206 | } 207 | } 208 | 209 | // Render our subtitles between start and stop times 210 | if (vStream.time >= start && vStream.time < end){ 211 | 212 | // Total display width is a function of character size 213 | int width = (vStream.width/CHAR_X)/vStream.scale; 214 | int height = (vStream.height/CHAR_Y)/vStream.scale; 215 | 216 | // Calculate bounding box for our subtitles 217 | int tBox_X = MAX_CHAR+2, tBox_Y = NUM_ROWS+2; 218 | int bLoc_X = (width/2) - (tBox_X/2), bLoc_Y = (height-2) - tBox_Y; 219 | 220 | // Tracker variables 221 | int x_Pos = 0; 222 | int y_Pos = 0; 223 | 224 | // Render our text box 225 | for (int y=0; y= bLoc_X && y >= bLoc_Y) && (x < bLoc_X+tBox_X && y < bLoc_Y+tBox_Y)) *(*frame+(x+(y*(width+1)))) = ' '; 229 | // Render text within the bounding box 230 | if ((x >= bLoc_X+1 && y >= bLoc_Y+1) && (x < bLoc_X+tBox_X-1 && y < bLoc_Y+tBox_Y-1)) *(*frame+(x+(y*(width+1)))) = vStream.subs[y_Pos++/(tBox_X-2)][x_Pos++%(tBox_X-2)]; 231 | } 232 | } 233 | 234 | } 235 | // Update our file pointer and subtitles 236 | else if (vStream.time >= end){ 237 | vStream.subPos++; 238 | currLine = (int) *(vStream.subTimes+(vStream.subPos)*3+2); 239 | int subPos = vStream.subPos; 240 | if (vStream.fpPos <= currLine+1){ 241 | while(fgets(buffer, sizeof(buffer), subFile) != NULL){ 242 | if (vStream.fpPos == currLine){ 243 | prepSubs(buffer); 244 | strcpy(vStream.subs[0], buffer); 245 | reading = 1; 246 | } else if (reading){ 247 | prepSubs(buffer); 248 | strcpy(vStream.subs[1], buffer); 249 | vStream.fpPos++; 250 | reading = 0; 251 | break; 252 | } 253 | vStream.fpPos++; 254 | } 255 | } 256 | vStream.subPos = subPos; 257 | } 258 | 259 | } -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | #ifdef _WIN32 2 | #include 3 | #else 4 | #include 5 | #define clrscr() printf("\e[1;1H\e[2J") 6 | #endif 7 | 8 | #include "main.h" 9 | 10 | struct videoStream vStream; 11 | struct timespec wait; 12 | struct playback controls; 13 | 14 | FILE* subFile; 15 | regex_t matchTime; 16 | int debug; 17 | int skew; 18 | 19 | // Open up the file data stream and parse frames 20 | void openStream(struct vStreamArgs* args){ 21 | 22 | char *filename = args->file; 23 | struct vBuffer *videoBuffer = args->buffer; 24 | 25 | AVFormatContext *pFormatCtx = NULL; 26 | AVCodecContext *pCodecCtx = NULL; 27 | AVCodec *pCodec = NULL; 28 | AVFrame *pFrame = NULL; 29 | AVFrame *pFrameRGB = NULL; 30 | 31 | double enableFramePacing = 0; 32 | int frameAdjustment = 0; 33 | 34 | // Open video file 35 | if(avformat_open_input(&pFormatCtx, filename, NULL, NULL) != 0) { 36 | vStream.bufferEnd = true; //use this to indicate to other threads that we failed 37 | pthread_exit((void *)1); // Couldn't open file 38 | } 39 | 40 | //Get stream info 41 | if(avformat_find_stream_info(pFormatCtx, NULL) < 0) { 42 | vStream.bufferEnd = true; //use this to indicate to other threads that we failed 43 | pthread_exit((void *)2); // Couldn't find stream information 44 | } 45 | 46 | // Find first video stream 47 | int i = 0; 48 | int stream = -1; 49 | 50 | // Walk over all streams until we find the first video stream 51 | for(int i=0; inb_streams; i++){ 52 | if(pFormatCtx->streams[i]->codecpar->codec_type == AVMEDIA_TYPE_VIDEO){ 53 | stream = i; 54 | break; 55 | } 56 | } 57 | 58 | if(vStream.fps == 0.0){ 59 | vStream.fps = (double) pFormatCtx->streams[stream]->avg_frame_rate.num / (double) pFormatCtx->streams[stream]->avg_frame_rate.den; 60 | vStream.srcFps = vStream.fps; 61 | } 62 | else{ 63 | enableFramePacing = (double) pFormatCtx->streams[stream]->avg_frame_rate.num / (double) pFormatCtx->streams[stream]->avg_frame_rate.den; 64 | frameAdjustment = getNiceFramerate(enableFramePacing); 65 | vStream.srcFps = enableFramePacing; 66 | } 67 | vStream.height = pFormatCtx->streams[stream]->codecpar->height; 68 | vStream.width = pFormatCtx->streams[stream]->codecpar->width*2; 69 | 70 | if (stream == -1) pthread_exit((void *)3); // No video streams found 71 | 72 | //Try to find the video codec 73 | pCodec = avcodec_find_decoder(pFormatCtx->streams[stream]->codecpar->codec_id); 74 | if (pCodec == NULL) pthread_exit((void *)4); // Unsupported codec 75 | 76 | // Copy the found codec 77 | pCodecCtx = avcodec_alloc_context3(pCodec); 78 | 79 | // Copy codec parameters and open the context 80 | avcodec_parameters_to_context(pCodecCtx, pFormatCtx->streams[stream]->codecpar); 81 | if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) pthread_exit((void *)5); //Could not open codec 82 | 83 | // Alocate a frame 84 | pFrame = av_frame_alloc(); 85 | pFrameRGB = av_frame_alloc(); 86 | if (pFrame == NULL) pthread_exit((void *)6); //Could not allocate AVFrame 87 | if (pFrameRGB == NULL) pthread_exit((void *)7); 88 | 89 | // Prepare a buffer for frame conversion 90 | int numBytes; 91 | uint8_t *buffer = NULL; 92 | 93 | numBytes = av_image_get_buffer_size(AV_PIX_FMT_GRAY8, pCodecCtx->width, pCodecCtx->height, ALIGNMENT); 94 | buffer = av_malloc(numBytes*sizeof(uint8_t)); 95 | 96 | avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_GRAY8, pCodecCtx->width, pCodecCtx->height); 97 | 98 | struct SwsContext *sws_ctx = NULL; 99 | int frameFinished; 100 | AVPacket packet; 101 | 102 | sws_ctx = sws_getContext(pCodecCtx->width, 103 | pCodecCtx->height, 104 | pCodecCtx->pix_fmt, 105 | pCodecCtx->width, 106 | pCodecCtx->height, 107 | AV_PIX_FMT_GRAY8, 108 | SWS_BILINEAR, 109 | NULL, 110 | NULL, 111 | NULL 112 | ); 113 | 114 | for(int frame=0; av_read_frame(pFormatCtx, &packet) >= 0; frame++){ 115 | // Check that the packet is from our stream 116 | if (packet.stream_index == stream){ 117 | // Decode frame 118 | avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); 119 | 120 | // Check that this is a video frame 121 | if (frameFinished){ 122 | // Convert frame to RGB image 123 | sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, 124 | pFrame->linesize, 0, pCodecCtx->height, 125 | pFrameRGB->data, pFrameRGB->linesize); 126 | 127 | vStream.frameCount++; 128 | vStream.realTime = (vStream.frameCount-(vStream.bufferLength*(vStream.srcFps/vStream.fps)))/vStream.srcFps; 129 | if (enableFramePacing && ((frame)%(int) ((ceil(enableFramePacing))/(vStream.fps)))) continue; 130 | // Wait until there is room in the frame buffer 131 | while(vStream.bufferLength >= MAX_BUFFER) nanosleep(&wait, (struct timespec*) NULL); 132 | // Write the new frame to the buffer 133 | writeBuffer(&videoBuffer, pFrameRGB); 134 | 135 | } 136 | } 137 | 138 | 139 | // Free the packet we allocated 140 | av_free_packet(&packet); 141 | 142 | if (controls.cease_execution) break; 143 | } 144 | vStream.bufferEnd = true; 145 | 146 | // Free the RGB image 147 | av_free(buffer); 148 | //writeBuffer(videoBuffer, pFrameRGB, 1); 149 | av_free(pFrameRGB); 150 | 151 | // Free the YUV frame 152 | av_free(pFrame); 153 | 154 | // Close the codecs 155 | avcodec_close(pCodecCtx); 156 | 157 | // Close the video file 158 | avformat_close_input(&pFormatCtx); 159 | 160 | pthread_exit((void *)0); 161 | } 162 | 163 | // Write new frame data to the tail of the buffer queue 164 | void writeBuffer(struct vBuffer **buffer, AVFrame *frame){ 165 | // Wait for the mutex 166 | while (pthread_mutex_trylock(&vStream.mutex) != 0) nanosleep(&wait, (struct timespec*) NULL);; 167 | 168 | // Find the end of the buffer 169 | while ((*buffer)->next != NULL) { (*buffer) = (*buffer)->next; } 170 | // Allocate the buffer 171 | (*buffer)->next = malloc(sizeof(struct videoBuffer*)+sizeof(uint8_t*) * vStream.width*vStream.height); 172 | 173 | // Handle the NULL head frame buffer 174 | if (frame != NULL){ 175 | // Convert frame data to a grayscale represenation and store it 176 | (*buffer)->next->frame = grayscale(frame, vStream.width, vStream.height); 177 | } 178 | 179 | // Update frame buffer 180 | (*buffer)->next->next = NULL; 181 | vStream.bufferLength++; 182 | 183 | pthread_mutex_unlock(&vStream.mutex); 184 | } 185 | 186 | // Return frame buffer at the head of our queue 187 | struct vBuffer* readBuffer(struct vBuffer **buffer){ 188 | struct vBuffer *nextBuffer; 189 | 190 | // don't bother trying to do anything if there's no need to do work 191 | while ((vStream.bufferLength <= 2 && !vStream.bufferEnd)) nanosleep(&wait, (struct timespec*) NULL);; 192 | // Wait for the mutex so we don't cause any issues 193 | while (pthread_mutex_trylock(&vStream.mutex) != 0) nanosleep(&wait, (struct timespec*) NULL); 194 | 195 | if ((*buffer)->next != NULL) { 196 | // Update buffer and queue 197 | nextBuffer = (*buffer)->next; 198 | free((*buffer)->frame); 199 | free(*buffer); 200 | vStream.bufferLength--; 201 | } 202 | 203 | pthread_mutex_unlock(&vStream.mutex); 204 | return &(*nextBuffer); 205 | } 206 | 207 | // Load array of subtitle time codes 208 | void loadSubs(){ 209 | 210 | char buffer[MAX_CHAR]; 211 | 212 | // Open our file for reading 213 | subFile = fopen(vStream.subFile, "r"); 214 | 215 | // Return if no file provided 216 | if (!subFile) return; 217 | 218 | int lines = 0; 219 | 220 | // Determine how much space we need for storing data 221 | while(fgets(buffer, sizeof(buffer), subFile) != NULL){ 222 | if(!regexec(&matchTime, buffer, 0, NULL, 0)) lines++; 223 | } 224 | 225 | // Reset file pointer 226 | rewind(subFile); 227 | 228 | // Allocate space for our sub data 229 | vStream.subTimes = malloc(sizeof(double) * lines * 3); 230 | int count = 0; 231 | lines = 0; 232 | 233 | // Write our subtitle data 234 | // Format [start time, end time, first line of text, ...] 235 | while(fgets(buffer, sizeof(buffer), subFile) != NULL){ 236 | if(!regexec(&matchTime, buffer, 0, NULL, 0)){ 237 | *(vStream.subTimes+(count++)) = decodeTime(buffer, 0); 238 | *(vStream.subTimes+(count++)) = decodeTime(buffer, 1); 239 | *(vStream.subTimes+(count++)) = lines+1; 240 | } 241 | lines++; 242 | } 243 | 244 | // Reset file pointer 245 | rewind(subFile); 246 | 247 | } 248 | 249 | // Run continuously in the background rendering frames when needed 250 | void *renderingEngine(struct vBuffer *buffer){ 251 | 252 | if (vStream.subFile != NULL) loadSubs(); 253 | 254 | // Timing variables 255 | struct timeval start, curr, diff; 256 | gettimeofday(&start, 0x0); 257 | int frameDigits = 0; 258 | 259 | // Run until we hit the streamEnd frame 260 | while(1){ 261 | 262 | // Do not render when paused (sleep thread to not spin it) 263 | while(controls.pause) sleep(0.1); 264 | 265 | // Thread may be ready before the frames are, so wait for frames 266 | while(vStream.bufferLength <= 0) { 267 | if (vStream.bufferEnd) pthread_exit((void*)1); 268 | nanosleep(&wait, (struct timespec*) NULL); 269 | } 270 | 271 | if (frameDigits == 0) frameDigits = getNiceFramerate(vStream.srcFps); 272 | 273 | // Update time tracking 274 | gettimeofday(&curr, 0x0); 275 | timeval_subtract(&diff, &curr, &start); 276 | 277 | // If enough time has passed between the last frame and this one render it 278 | // Alternatively, if we are fast forwarding loading frames as they come into the buffer 279 | if (diff.tv_usec >= (1/vStream.fps*1000000) || controls.fast_forward){ 280 | // Update our timing variable 281 | gettimeofday(&start, 0x0); 282 | // Pull the new frame data from the buffer 283 | while(buffer->next == NULL); 284 | buffer = readBuffer(&buffer); 285 | vStream.time = vStream.time + (1/(vStream.fps)); 286 | 287 | // Account for possible frame skew when rendering at different framerates 288 | if (!skew && (vStream.time > 1.02*vStream.realTime || vStream.time < 0.98*vStream.realTime)) vStream.time = vStream.realTime; 289 | 290 | // Pass frame data to renderer 291 | renderFrame(buffer->frame, vStream.width, vStream.height); 292 | 293 | if ((buffer->next == NULL) && vStream.bufferEnd) break; 294 | 295 | } 296 | 297 | nanosleep(&wait, (struct timespec*) NULL); 298 | 299 | if (controls.cease_execution) break; 300 | } 301 | 302 | // Close the subtitle file 303 | if (vStream.subFile != NULL) fclose(subFile); 304 | 305 | // Free memory 306 | free(buffer); 307 | pthread_exit((void *)0); 308 | } 309 | 310 | // Spawn all neccessary threads and manage them 311 | int main(int argc, char *argv[]){ 312 | 313 | char* filename = NULL; 314 | char* subfile = NULL; 315 | char* flag; 316 | int slowMode = 0; 317 | int disableRenderer = 0; 318 | 319 | // Initialize our control structure 320 | controls = (struct playback) { 321 | .pause = false, 322 | .fast_forward = false, 323 | .cease_execution = false 324 | }; 325 | 326 | wait.tv_nsec = 1000000; 327 | 328 | // Initialize our video buffer 329 | struct vBuffer *videoBuffer = malloc(sizeof(struct videoBuffer*)); 330 | videoBuffer->frame = NULL; 331 | videoBuffer->next = NULL; 332 | 333 | // Initialize the parameters of our video stream 334 | vStream.bufferLength = 0; 335 | vStream.time = 0; 336 | vStream.subTimes = NULL; 337 | vStream.fpPos = 0; 338 | vStream.subPos = 0; 339 | vStream.fps = 0.0; 340 | vStream.scale = 1; 341 | vStream.bufferEnd = false; 342 | pthread_mutex_init(&vStream.mutex, NULL); 343 | 344 | // Setup thread tracking 345 | pthread_t threads[NUM_THREADS]; 346 | 347 | for (int i=1; i 1) vStream.fps = (((int) *(*(argv+i))-'0')*10) + ((int) (*(*(argv+i)+1)-'0')); 376 | else vStream.fps = ((int) (*(*(argv+i))-'0')); 377 | } 378 | 379 | if (!strcmp(flag, "-h")){ 380 | printf("Usage ./telnetflix -f [ -s ]\n"); 381 | return 0; 382 | } 383 | } 384 | 385 | if (filename == NULL){ 386 | printf("Error, no file specified\n"); 387 | return -1; 388 | } 389 | 390 | // Initialize display 391 | if (!disableRenderer){ 392 | initscr(); 393 | noecho(); 394 | curs_set(FALSE); 395 | } 396 | 397 | // Setup regex 398 | regcomp(&matchTime, MATCH_EXPR , 0); 399 | 400 | // Register codecs 401 | av_register_all(); 402 | 403 | // Prepare data for the stream 404 | struct vStreamArgs *vArgs = malloc(sizeof(struct vStreamArgs*)); 405 | vArgs->file = filename; 406 | vArgs->buffer = videoBuffer; 407 | 408 | // Create and track our threads 409 | pthread_create(&threads[0], NULL, (void * (*)(void *))openStream, vArgs); 410 | if (slowMode) sleep(4); 411 | pthread_create(&threads[1], NULL, (void * (*)(void *))renderingEngine, videoBuffer); 412 | pthread_create(&threads[2], NULL, inputHandler, NULL); 413 | pthread_join(threads[0], NULL); 414 | pthread_join(threads[1], NULL); 415 | pthread_cancel(threads[2]); 416 | 417 | // Free allocated memory 418 | free(vArgs); 419 | if(vStream.subTimes != NULL) free(vStream.subTimes); 420 | pthread_mutex_destroy(&vStream.mutex); 421 | 422 | // Terminate our screen 423 | endwin(); 424 | 425 | return 0; 426 | 427 | } 428 | --------------------------------------------------------------------------------