├── README.md ├── awk-videoplayer.cfg ├── codecs ├── generic.codec └── yuyv422.codec ├── play ├── screenshots ├── rgb24.png ├── rgb565.png └── rgb8.png ├── scripts └── convert.sh └── src ├── awk-videoplayer.gawk ├── config.gawk ├── decfnc.gawk ├── delay.gawk ├── draw.gawk ├── hsync.gawk ├── ord.gawk └── yuv.gawk /README.md: -------------------------------------------------------------------------------- 1 | # awk-videoplayer 2 | 3 | A little experiment I wrote during the 2020 christmas holidays 4 | 5 | This will play (to some extent) many video files in raw RGB, BGR, YUV and GRAY pixel formats. 6 | 7 | The `play` wrapper script utilizes `ffmpeg` to do the conversion from whatever format to the raw desired format, plus handle scaling to a smaller format, the size of your terminal. 8 | 9 | Audio will most likely be out of sync or stutter, since that is handled by `ffmpeg` and the video by the `awk-videoplayer`. 10 | 11 | With the help of e36freak on #awk (Freenode) I got a version together which can handle streaming input, so there is no need to first convert and then play, but input can be streamed directly from `ffmpeg` stdout to the player 12 | 13 | Usage: `play []` 14 | 15 | Example pixelformats are: gray, rgb8, rgb565, rgb24, yuyv422. For all supported pixel formats see `awk-videoplayer.cfg` 16 | 17 | You can modify either the `w` and `h` manually or set the `size` variable in the `play` script to change your needs. 18 | 19 | Only the even frames are displayed to come close to real-time video playback. 20 | On my hardware 24fps results in about 50% playback speed. Displaying only the even frames (~12fps) is around real-time playback speed 21 | It uses an optimized `draw()` function from my `awk-glib` (see: https://github.com/patsie75/awk-glib ) 22 | 23 | During new years weekend of 2021 the code has been revamped to try and improve performance by making use of multithreading/processing with less than great results. 24 | It looks like most performance is lost in drawing the actual displayed picture and not in calculating the data, so multithreading this part has not increased performance that much. 25 | The default thread count is 2, meaning the player will spawn 2 decoding threads/processes next to the main player. 26 | If multithreading is not working properly for you, set the `` to 0 (zero) 27 | 28 | This script is currently in a testing phase. Don't expect too much from it. If this breaks anything, you'll get to keep all the pieces. 29 | 30 | Example video can be seen here: https://youtu.be/QVQkG5bBcOo 31 | And Bad Apple! playing at a buttery smooth 30fps and audio in-sync: https://youtu.be/I8AwZDdk8oQ 32 | 33 | Example screenshots in 8, 16 and 24 bits:
34 | ![8 bits](/screenshots/rgb8.png)
35 | ![16 bits](/screenshots/rgb565.png)
36 | ![24 bits](/screenshots/rgb24.png)
37 | -------------------------------------------------------------------------------- /awk-videoplayer.cfg: -------------------------------------------------------------------------------- 1 | ## 2 | ## All awk-videoplayer supported pixel formats must be defined here 3 | ## 4 | 5 | #[pixelformat] 6 | # bpp = (mandatory, number of bits per pixel) 7 | # macro_pix = (default: 1, number of pixels in one datablock) 8 | # codec = "" (default: "generic", codec name used from codecs folder) 9 | # decfnc = "" (default: , pixel decode function from src/decfnc.gawk) 10 | # offset = "" (optional, comma separated order of color bytes in one macro pixel) 11 | 12 | [gray] 13 | bpp = 8 14 | 15 | [rgb8] 16 | bpp = 8 17 | 18 | [bgr8] 19 | bpp = 8 20 | 21 | [rgb565] 22 | bpp = 16 23 | 24 | [bgr565] 25 | bpp = 16 26 | 27 | [rgb24] 28 | bpp = 24 29 | offset = r,g,b 30 | 31 | [bgr24] 32 | bpp = 24 33 | decfnc = rgb24 34 | offset = b,g,r 35 | 36 | [argb] 37 | bpp = 32 38 | decfnc = rgb24 39 | offset = a,r,g,b 40 | 41 | [rgba] 42 | bpp = 32 43 | decfnc = rgb24 44 | offset = r,g,b,a 45 | 46 | [abgr] 47 | bpp = 32 48 | decfnc = rgb24 49 | offset = a,b,g,r 50 | 51 | [bgra] 52 | bpp = 32 53 | decfnc = rgb24 54 | offset = b,g,r,a 55 | 56 | [yuyv422] 57 | bpp = 16 58 | macro_pix = 2 59 | offset = y1,u,y2,v 60 | #codec = yuyv422 61 | 62 | [uyvy422] 63 | bpp = 16 64 | macro_pix = 2 65 | decfnc = yuyv422 66 | offset = u,y1,v,y2 67 | 68 | [yvyu422] 69 | bpp = 16 70 | macro_pix = 2 71 | decfnc = yuyv422 72 | offset = y1,v,y2,u 73 | 74 | -------------------------------------------------------------------------------- /codecs/generic.codec: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gawk -f 2 | @include "src/decfnc.gawk" 3 | 4 | BEGIN { 5 | RS = ".{" width * bytes_per_pix "}" 6 | 7 | byte_inc = bytes_per_pix * macro_pix 8 | 9 | # get color offsets/order within data 10 | n = split(offset, arr, ",") 11 | for (i=0; i&1 | awk ' 19 | ! (1 in duration) && match($0, /^ Duration: ([0-9]+):([0-9]+):([0-9]+\.[0-9]+),/, duration) { } 20 | ! (1 in info) && match($0, /Stream #[0-9]:[0-9].*: Video: .* ([0-9]+)x([0-9]+) .* DAR ([0-9]+):([0-9]+).* ([0-9]+(\.[0-9]+)?) fps/, info) { } 21 | ! (1 in frames) && match($0, /frame= *([0-9]+)/, frames) { } 22 | END { printf("%d %d %d %d %d %.2f %.2f\n", info[1], info[2], info[3], info[4], frames[1], duration[1]*3600 + duration[2]*60 + duration[3], info[5]) } 23 | ' ) 24 | 25 | ## find proper/maximum size of our video 26 | aspectwidth=$( awk '{ print $3 }' <<<"$meta" ) 27 | aspectheight=$( awk '{ print $4 }' <<<"$meta" ) 28 | for size in {2..100..2}; do 29 | [[ $(( ($size+2) * $aspectwidth )) -ge $maxwidth ]] && break 30 | [[ $(( ($size+2) * $aspectheight )) -ge $((maxheight - 2)) ]] && break 31 | done 32 | 33 | ## scale to correct width x height 34 | w=$(( aspectwidth * size )) 35 | h=$(( aspectheight * size )) 36 | 37 | ## ffmpeg options, decode to raw, uncompressed byte-data video in certain pixelformat 38 | ffopts=( -f alsa default -vf "scale=w=$w:h=$h" -vcodec rawvideo -f rawvideo -pix_fmt "$pix_fmt" ) 39 | 40 | ## gawk options, needs `-b` or `LC_ALL=C` to parse byte-characters and not multi-byte characters 41 | awkopts=( -b -v width="$w" -v height="$h" -v pix_fmt="$pix_fmt" -v threads="$threads" -v meta="$meta" ) 42 | 43 | ## start ffmpeg conversion and display video 44 | ffmpeg -i "$1" "${ffopts[@]}" - 2>/dev/null | gawk "${awkopts[@]}" -f src/awk-videoplayer.gawk 45 | -------------------------------------------------------------------------------- /screenshots/rgb24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patsie75/awk-videoplayer/514bf61efb3317ffb7d0072b810ff2b0a703556d/screenshots/rgb24.png -------------------------------------------------------------------------------- /screenshots/rgb565.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patsie75/awk-videoplayer/514bf61efb3317ffb7d0072b810ff2b0a703556d/screenshots/rgb565.png -------------------------------------------------------------------------------- /screenshots/rgb8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/patsie75/awk-videoplayer/514bf61efb3317ffb7d0072b810ff2b0a703556d/screenshots/rgb8.png -------------------------------------------------------------------------------- /scripts/convert.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -ge 2 ]; then 4 | #ffmpeg -t "${3:-10}" -i "$1" -vf scale=192:108 -vcodec rawvideo -f rawvideo -pix_fmt rgb8 "$2" 5 | #ffmpeg -t "${3:-10}" -i "$1" -vf scale=192:108 -vcodec rawvideo -f rawvideo -pix_fmt rgb565 "$2" 6 | ffmpeg -t "${3:-10}" -i "$1" -vf scale=192:108 -vcodec rawvideo -f rawvideo -pix_fmt rgb24 "$2" 7 | else 8 | echo "Usage: $0 [time]" >&2 9 | exit 1 10 | fi 11 | 12 | -------------------------------------------------------------------------------- /src/awk-videoplayer.gawk: -------------------------------------------------------------------------------- 1 | #!/usr/bin/gawk -f 2 | 3 | ## modified draw function used from awk-glib for displaying data 4 | ## awk-glib source: https://github.com/patsie75/awk-glib 5 | @include "src/draw.gawk" 6 | @include "src/hsync.gawk" 7 | @include "src/decfnc.gawk" 8 | @include "src/config.gawk" 9 | @include "src/delay.gawk" 10 | 11 | ## convert seconds to human readable time 12 | function durationtotime(duration) { 13 | return sprintf("%02dh%02dm%03.1fs", duration / 3600, (duration/60)%60, duration%60) 14 | } 15 | 16 | ## show a status information bar top-left 17 | function status(vid) { 18 | printf("\033[H%dx%d@%d (%s), %s/%s %5.1f%% \033[97;44m%s\033[0m frame: %6s (dropped: %d), fps: %4.1f cur/%4.1f avg", vid["width"], vid["height"], vid["fps"], vid["pix_fmt"], vid["time"], durationtotime(vid["duration"]), vid["frame"]*100/vid["frames"], bar(vid["frame"], vid["frames"], 10), vid["frame"], vid["skipped"], vid["curfps"], vid["avgfps"]) 19 | } 20 | 21 | ## draw progress bar 22 | function bar(val, max, barsize, str, len, full, part, i) { 23 | str = "" 24 | len = length(bargraph) - 1 25 | 26 | full = int(val*barsize/max) 27 | part = (val*barsize/max) % 1 28 | 29 | while (i < full) { str = str sprintf("%s", bargraph[8]); i++ } 30 | if (part >= 1/len) { str = str sprintf("%s", bargraph[int(part*len)]); i++ } 31 | while (i < barsize) { str = str sprintf("%s", bargraph[0]); i++ } 32 | 33 | return str 34 | } 35 | 36 | ## initialize video player 37 | BEGIN { 38 | # progress bar graphics 39 | bargraph[0] = " " 40 | bargraph[1] = "▏" 41 | bargraph[2] = "▎" 42 | bargraph[3] = "▍" 43 | bargraph[4] = "▌" 44 | bargraph[5] = "▋" 45 | bargraph[6] = "▊" 46 | bargraph[7] = "▉" 47 | bargraph[8] = "█" 48 | 49 | # load configuration 50 | load_cfg(cfg, "awk-videoplayer.cfg") 51 | 52 | # set video pixelformat 53 | vid["pix_fmt"] = pix_fmt ? pix_fmt : "rgb24" 54 | 55 | # check if pixelformat has a known configuration 56 | if ( !(vid["pix_fmt"] in cfg) ) 57 | { 58 | printf("ERR: Unknown pixel format: \"%s\"\nUse one of:", vid["pix_fmt"]) 59 | for (fmt in cfg) printf(" \"%s\"", fmt) 60 | printf("\n\n") 61 | exit 1 62 | } 63 | 64 | if (split(meta, a) == 7) { 65 | vid["orgwidth"] = int(a[1]) 66 | vid["orgheight"] = int(a[2]) 67 | vid["aspectwidth"] = int(a[3]) 68 | vid["aspectheight"] = int(a[4]) 69 | vid["frames"] = int(a[5]) 70 | vid["duration"] = a[6] 71 | vid["fps"] = int(a[7] + 0.5) 72 | } else { 73 | printf("Not enough arguments (7) for meta: \"%s\"\n", meta) 74 | exit 0 75 | } 76 | 77 | # set rest of video parameters 78 | vid["width"] = width ? width : 192 79 | vid["height"] = height ? height : 108 80 | vid["threads"] = length(threads) ? threads : 2 81 | vid["bpp"] = cfg[vid["pix_fmt"]]["bpp"] 82 | vid["bytes_per_pix"] = int(vid["bpp"] / 8) 83 | vid["macro_pix"] = cfg[vid["pix_fmt"]]["macro_pix"] ? cfg[vid["pix_fmt"]]["macro_pix"] : 1 84 | vid["codec"] = cfg[vid["pix_fmt"]]["codec"] ? cfg[vid["pix_fmt"]]["codec"] : "generic" 85 | vid["decfnc"] = decfnc = cfg[vid["pix_fmt"]]["decfnc"] ? "dec_" cfg[vid["pix_fmt"]]["decfnc"] : "dec_" vid["pix_fmt"] 86 | vid["offset"] = cfg[vid["pix_fmt"]]["offset"] ? cfg[vid["pix_fmt"]]["offset"] : "" 87 | vid["byte_inc"] = vid["bytes_per_pix"] * vid["macro_pix"] 88 | 89 | # clear video screen 90 | clear(vid, "0;0;0") 91 | 92 | # split offset into array 93 | n = split(vid["offset"], arr, ",") 94 | for (i=0; i 0) { 142 | skip-- 143 | vid["skipped"]++ 144 | } else draw(vid) 145 | 146 | if ( !(vid["frame"] % 13) ) 147 | status(vid) 148 | } 149 | } 150 | 151 | 152 | ## multi-threaded 153 | (vid["threads"] > 0) { 154 | # thread number to pass data to 155 | threadnr = ((NR-1) % vid["height"] % vid["threads"]) 156 | 157 | # send data to thread 158 | printf("%s", RT) |& thread[threadnr] 159 | 160 | # if this is the last line (hsync) then draw the frame 161 | if (hsync(vid)) 162 | { 163 | # threads need data and time to process. delay reading with one frame 164 | if (vid["frame"] > 1) 165 | { 166 | # thread data returns newline separated 167 | RS = "\n" 168 | 169 | # read data from all threads 170 | for (threadnr=0; threadnr 0) { 190 | skip-- 191 | vid["skipped"]++ 192 | } else draw(vid) 193 | 194 | if ( !(vid["frame"] % 13) ) 195 | status(vid) 196 | } 197 | } 198 | 199 | END { 200 | status(vid) 201 | 202 | # reenable cursor 203 | cursor("on") 204 | 205 | # close running threads 206 | for (i=0; i 0) { 6 | ## have a [label] tag to init-style switch labels 7 | if (match($0, /^\[([^]]*)\]$/, l)) 8 | label = l[1] 9 | 10 | ## skip comments and split key=value pairs 11 | if ( ($0 !~ /^ *(#|;)/) && (match($0, /([^=]+)=(.+)/, keyval) > 0) ) { 12 | ## strip leading/trailing spaces and doublequotes 13 | gsub(/^ *"?|"? *$/, "", keyval[1]) 14 | gsub(/^ *"?|"? *$/, "", keyval[2]) 15 | 16 | cfg[label][tolower(keyval[1])] = keyval[2] 17 | n++ 18 | } 19 | 20 | } 21 | 22 | close(file) 23 | return(n) 24 | } 25 | -------------------------------------------------------------------------------- /src/decfnc.gawk: -------------------------------------------------------------------------------- 1 | @include "src/ord.gawk" 2 | @include "src/yuv.gawk" 3 | 4 | function dec_gray(data, byte, offset, gray) { 5 | gray = ORD[data[byte]] 6 | return sprintf("%d;%d;%d", gray, gray, gray) 7 | } 8 | 9 | function dec_rgb8(data, byte, offset, rgb) { 10 | rgb = ORD[data[byte]] 11 | return sprintf("%d;%d;%d", and(rgb,0xE0) / 0xE0 * 0xFF, and(rgb,0x1C) / 0x1C * 0xFF, and(rgb,0x03) / 0x03 * 0xFF) 12 | } 13 | 14 | function dec_bgr8(data, byte, offset, rgb) { 15 | rgb = ORD[data[byte]] 16 | return sprintf("%d;%d;%d", and(rgb,0x07) / 0x07 * 0xFF, and(rgb,0x38) / 0x38 * 0xFF, and(rgb,0xC0) / 0xC0 * 0xFF) 17 | } 18 | 19 | function dec_rgb565(data, byte, offset, rgb) { 20 | rgb = ORD[data[byte]] + ORD[data[byte+1]] * 256 21 | return sprintf("%d;%d;%d", and(rgb,0xF800) / 0xF800 * 0xFF, and(rgb,0x07E0) / 0x07E0 * 0xFF, and(rgb,0x1F) / 0x1F * 0xFF) 22 | } 23 | 24 | function dec_bgr565(data, byte, offset, rgb) { 25 | rgb = ORD[data[byte]] + ORD[data[byte+1]] * 256 26 | return sprintf("%d;%d;%d", and(rgb,0x1F) / 0x1F * 0xFF, and(rgb,0x07E0) / 0x07E0 * 0xFF, and(rgb,0xF800) / 0xF800 * 0xFF) 27 | } 28 | 29 | function dec_rgb24(data, byte, offset) { 30 | return sprintf("%d;%d;%d", ORD[data[byte + offset["r"]]], ORD[data[byte + offset["g"]]], ORD[data[byte + offset["b"]]]) 31 | } 32 | 33 | function dec_yuyv422(data, byte, offset, y1, u, y2, v) { 34 | y1 = ORD[data[byte + offset["y1"] ]] 35 | u = ORD[data[byte + offset["u"] ]] 36 | y2 = ORD[data[byte + offset["y2"] ]] 37 | v = ORD[data[byte + offset["v"] ]] 38 | 39 | return yuv2rgb(y1, u, v) " " yuv2rgb(y2, u, v) 40 | } 41 | 42 | -------------------------------------------------------------------------------- /src/delay.gawk: -------------------------------------------------------------------------------- 1 | #/usr/bin/gawk -f 2 | 3 | ## global vars: now, prev 4 | 5 | function delay(target, skip, onesec, i) { 6 | skip = 0 7 | onesec = 0 8 | now = timex() 9 | 10 | # init sliding window 11 | if ( !(0 in window) ) 12 | for (i=0; i<(target-1); i++) 13 | window[i] = 1/target 14 | 15 | # too slow, return number of frames to skip 16 | if ( (now-prev) > (1/target) ) 17 | skip = int( (now-prev) / (1/target) ) 18 | else { 19 | # calculate sliding FPS window 20 | for (i=1; i= 0.5 ) 20 | { 21 | vid["curfps"] = vid["framecnt"] / (vid["now"] - vid["then"]) 22 | vid["avgfps"] = vid["frame"] / (vid["now"] - vid["start"]) 23 | vid["framecnt"] = 0 24 | vid["then"] = vid["now"] 25 | } 26 | return 1 27 | } 28 | return 0 29 | } 30 | -------------------------------------------------------------------------------- /src/ord.gawk: -------------------------------------------------------------------------------- 1 | BEGIN { 2 | for (i=0; i<256; i++) 3 | ORD[sprintf("%c",i)] = i; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /src/yuv.gawk: -------------------------------------------------------------------------------- 1 | function clip(a, b, c) { return (a <= b) ? b : (a >= c) ? c : a } 2 | 3 | function yuv2rgb(y, u, v, c, d, e, r, g, b) { 4 | c = (y - 16) * 1.164383 5 | d = u - 128 6 | e = v - 128 7 | 8 | r = clip( c + (1.596027 * e), 0, 255 ) 9 | g = clip( c - (0.391762 * d) - (0.812968 * e), 0, 255 ) 10 | b = clip( c + (2.017232 * d) , 0, 255 ) 11 | 12 | return sprintf("%d;%d;%d", r, g, b) 13 | } 14 | --------------------------------------------------------------------------------