├── LICENSE ├── Makefile ├── README.md ├── decoder.cpp ├── decoder.html └── othercode.js /LICENSE: -------------------------------------------------------------------------------- 1 | see https://github.com/cisco/openh264 2 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TOTALMEMORY:=-s TOTAL_MEMORY=33554432 2 | 3 | all: decoder.js.gz WebGLCanvas.js 4 | 5 | h264symbols='["_open_decoder","_close_decoder","_decode_h264buffer","_decode_nal"]' 6 | 7 | decoder.js.gz: decoder.js 8 | gzip -f -k decoder.js 9 | 10 | decoder.js: decoder_em.js othercode.js 11 | cat decoder_em.js othercode.js > decoder.js 12 | 13 | decoder_em.js: decoder.cpp openh264/libopenh264.so.0 14 | em++ -O1 $(TOTALMEMORY) -o $@ $^ -s EXPORTED_FUNCTIONS=$(h264symbols) -Iopenh264/codec/api/svc/ 15 | 16 | openh264: 17 | git clone git@github.com:cisco/openh264.git openh264 18 | 19 | openh264/libopenh264.so.0: openh264 20 | cd openh264 && git checkout 41caf381520005c612a2089e070b35c045cc3e77 && emmake make OS=linux ARCH=asmjs 21 | 22 | WebGLCanvas.js: 23 | wget "https://raw.githubusercontent.com/mbebenita/Broadway/master/Player/WebGLCanvas.js" 24 | 25 | .PHONY: clean cleanall distclean 26 | 27 | clean: 28 | rm -f decoder.js decoder.js.gz decoder_em.js 29 | 30 | cleanall: clean 31 | cd openh264; make clean 32 | 33 | distclean: clean 34 | rm -Rf openh264 WebGLCanvas.js 35 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Note: This project is NOT maintained. I don't provide any support for it. 2 | 3 | 4 | # OpenH264-js 5 | 6 | This is an example of the Cisco's OpenH264 library compiled 7 | to javascript with emscripten for running natively in browsers. 8 | 9 | This is very similar to eg https://github.com/mbebenita/Broadway and 10 | others. The main difference is that they use the BSD decoder which is 11 | limited to h264 baseline profile. Among others, that means no cabac. 12 | OpenH264 (http://www.openh264.org/) does support cabac. 13 | 14 | ## Features 15 | - CABAC decoding 16 | - Tested in Chrome v43 and Firefox 40 17 | - single nal decoding, makes it easy to decode any container externally 18 | - > 30 fps for 1280x720 main profile stream (cabac encoded) on Haswell i7 19 | 20 | ## Limitiations 21 | - Lot's of hardcoding in demo (decode.html). 22 | 23 | ## Build instructions 24 | * only tested on linux / emcc (Emscripten GCC-like replacement) 1.29.0 (commit fdf24b478e1b26c0362a1627aca49ef82fd53f6a) 25 | * Get emscripten (http://kripken.github.io/emscripten-site/docs/getting_started/Tutorial.html) 26 | 27 | * Run make 28 | 29 | The Makefile will download all dependencies and build everything. 30 | A small demo is provided in decoder.html. The demo uses Broadway's rendering library. 31 | 32 | 33 | ## Possible improvements 34 | 35 | There are a lot of copying going on, Several reasons: 36 | 37 | * A quick look didn't reveal any buffer control in OpenH264 (validity of data pointers are until next decode call?) 38 | * Broadway's yuv->rgb conversion needs back-to-back planes with stride = width: OpenH264 doesn't do that. 39 | 40 | ## Other points 41 | * closure-compiler, even with at whitespace level makes playback slower and more jittery. No clue why. 42 | -------------------------------------------------------------------------------- /decoder.cpp: -------------------------------------------------------------------------------- 1 | #include "codec_api.h" 2 | #include "codec_def.h" 3 | #include "codec_app_def.h" 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | 13 | extern "C" 14 | void openh264_log(void * ctx, int level, const char * msg) 15 | { 16 | emscripten_log(EM_LOG_CONSOLE, "openh264 trace %d %s\n", level, msg); 17 | } 18 | 19 | extern "C" 20 | void * open_decoder(void) 21 | { 22 | ISVCDecoder* decoder_ = NULL; 23 | if ( WelsCreateDecoder (&decoder_) ) { 24 | emscripten_log(EM_LOG_CONSOLE, "Create Decoder failed\n"); 25 | return NULL; 26 | } 27 | if (! decoder_) { 28 | emscripten_log(EM_LOG_CONSOLE, "Create Decoder failed (no handle)\n"); 29 | return NULL; 30 | } 31 | 32 | WelsTraceCallback cb = openh264_log; 33 | int32_t tracelevel = 3;//0x7fffffff; 34 | if ( decoder_->SetOption(DECODER_OPTION_TRACE_CALLBACK, (void*)&cb) ) 35 | { 36 | emscripten_log(EM_LOG_CONSOLE, "SetOption failed\n"); 37 | } 38 | if ( decoder_->SetOption(DECODER_OPTION_TRACE_LEVEL, &tracelevel) ) 39 | { 40 | emscripten_log(EM_LOG_CONSOLE, "SetOption failed\n"); 41 | } 42 | 43 | SDecodingParam decParam; 44 | memset (&decParam, 0, sizeof (SDecodingParam)); 45 | decParam.eOutputColorFormat = videoFormatI420; 46 | decParam.uiTargetDqLayer = UCHAR_MAX; 47 | decParam.eEcActiveIdc = ERROR_CON_SLICE_COPY; 48 | decParam.sVideoProperty.eVideoBsType = VIDEO_BITSTREAM_DEFAULT; 49 | 50 | if ( decoder_->Initialize (&decParam) ) { 51 | emscripten_log(EM_LOG_CONSOLE, "initialize failed\n"); 52 | return NULL; 53 | } 54 | 55 | return decoder_; 56 | } 57 | 58 | extern "C" 59 | void close_decoder(void * dec) 60 | { 61 | ISVCDecoder* decoder_ = (ISVCDecoder*)dec; 62 | if (decoder_ != NULL) { 63 | decoder_->Uninitialize(); 64 | WelsDestroyDecoder (decoder_); 65 | } 66 | } 67 | 68 | extern "C" 69 | int decode_nal(void * dec, unsigned char const * nal, size_t nalsz) 70 | { 71 | ISVCDecoder* decoder_ = (ISVCDecoder*)dec; 72 | uint8_t* data[3]; 73 | SBufferInfo bufInfo; 74 | memset (data, 0, sizeof (data)); 75 | memset (&bufInfo, 0, sizeof (SBufferInfo)); 76 | if (nalsz <= 0) { 77 | int32_t iEndOfStreamFlag = 1; 78 | decoder_->SetOption (DECODER_OPTION_END_OF_STREAM, &iEndOfStreamFlag); 79 | nalsz = 0; 80 | nal = NULL; 81 | } 82 | 83 | 84 | DECODING_STATE rv = decoder_->DecodeFrame2 (nal, (int) nalsz, data, &bufInfo); 85 | if (rv == 0 && bufInfo.iBufferStatus) { 86 | EM_ASM_({ 87 | frame_callback($0, $1, $2, $3, $4, $5, $6); 88 | }, 89 | data[0], 90 | data[1], 91 | data[2], 92 | bufInfo.UsrData.sSystemBuffer.iWidth, 93 | bufInfo.UsrData.sSystemBuffer.iHeight, 94 | bufInfo.UsrData.sSystemBuffer.iStride[0], 95 | bufInfo.UsrData.sSystemBuffer.iStride[1] 96 | ); 97 | return 1; 98 | } 99 | else if (rv != 0) { 100 | char statusstr[100] = {0}; 101 | if (rv & dsFramePending) strcat(statusstr,",FramePending"); 102 | if (rv & dsRefLost) strcat(statusstr,",RefLost"); 103 | if (rv & dsBitstreamError) strcat(statusstr,",BitstreamError"); 104 | if (rv & dsDepLayerLost) strcat(statusstr,",DepLayerLost"); 105 | if (rv & dsNoParamSets) strcat(statusstr,",NoParamSets"); 106 | if (rv & dsDataErrorConcealed) strcat(statusstr,",DataErrorConcealed"); 107 | if (rv & dsInvalidArgument) strcat(statusstr,",InvalidArgument"); 108 | if (rv & dsInitialOptExpected) strcat(statusstr,",InitialOptExcpected"); 109 | if (rv & dsOutOfMemory) strcat(statusstr,",OutOfMemory"); 110 | if (rv & dsDstBufNeedExpan) strcat(statusstr,",DstBufNeedExpan"); 111 | emscripten_log(EM_LOG_CONSOLE, "Decode failed: %#x - %s\n", rv, statusstr); 112 | return -1; 113 | } else 114 | return 0; 115 | 116 | // emscripten_log(EM_LOG_CONSOLE, " frame ready:%d\n" 117 | // " BsTimeStamp:%llu\n" 118 | // " OutYuvTimeS:%llu\n" 119 | // " Width :%d\n" 120 | // " Height :%d\n" 121 | // " Format :%d\n" 122 | // " Stride :%d %d\n" 123 | // ,bufInfo.iBufferStatus 124 | // ,bufInfo.uiInBsTimeStamp 125 | // ,bufInfo.uiOutYuvTimeStamp 126 | // ,bufInfo.UsrData.sSystemBuffer.iWidth 127 | // ,bufInfo.UsrData.sSystemBuffer.iHeight 128 | // ,bufInfo.UsrData.sSystemBuffer.iFormat 129 | // ,bufInfo.UsrData.sSystemBuffer.iStride[0] 130 | // ,bufInfo.UsrData.sSystemBuffer.iStride[1] 131 | // ); 132 | } 133 | 134 | ssize_t getnal(unsigned char const * mmaped, size_t offs, size_t sz) 135 | { 136 | size_t ofs = offs+3; 137 | int zerocnt = 0; 138 | 139 | if (offs >= sz) return -1; 140 | 141 | for(;ofs0); 174 | 175 | 176 | return 0; 177 | } 178 | 179 | 180 | #ifdef NATIVE 181 | #include 182 | unsigned char * mmap_open(const char * fname, size_t * sz) 183 | { 184 | FILE * f = fopen(fname, "rb"); 185 | fseek(f, 0, SEEK_END); 186 | *sz = ftell(f); 187 | return (unsigned char*)mmap(NULL, *sz, PROT_READ,MAP_SHARED, fileno(f), 0); 188 | } 189 | 190 | 191 | int main(int argc, char * argv[]) 192 | { 193 | void * h = open_decoder(); 194 | 195 | 196 | emscripten_log(EM_LOG_CONSOLE, "ready to decode\n"); 197 | size_t sz; 198 | const unsigned char * nalbuf = mmap_open(argv[1], &sz); 199 | size_t ofs = 0; 200 | ssize_t nalsz; 201 | 202 | do { 203 | nalsz = getnal(nalbuf, ofs, sz); 204 | const unsigned char * nal = nalbuf + ofs; 205 | 206 | decode_nal(h, nal, nalsz); 207 | 208 | ofs += nalsz; 209 | } while (nalsz>0); 210 | 211 | 212 | close_decoder(h); 213 | 214 | } 215 | #endif 216 | -------------------------------------------------------------------------------- /decoder.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /othercode.js: -------------------------------------------------------------------------------- 1 | function frame_callback(a,b,c,width,height,stride1,stride2) 2 | { 3 | var data = new Uint8Array(width*height*3/2); 4 | var x,y; 5 | for (y=0;y