import { ChildProcessWithoutNullStreams , spawn } from 'child_process' ; import { EventEmitter } from 'events' ; import * as stream from 'stream' ; import { AwbMode , ExposureMode , Flip , Rotation } from '..' ; import { getSharedArgs } from './shared-args' ; export enum Codec { H264 = 'H264' , MJPEG = 'MJPEG' } export enum SensorMode { AutoSelect = 0 , Mode1 = 1 , Mode2 = 2 , Mode3 = 3 , Mode4 = 4 , Mode5 = 5 , Mode6 = 6 , Mode7 = 7 } export interface StreamOptions { width ? : number ; height ? : number ; rotation ? : Rotation ; flip ? : Flip ; bitRate ? : number ; fps ? : number ; codec ? : Codec ; sensorMode ? : SensorMode ; shutter ? : number ; sharpness ? : number ; contrast ? : number ; brightness ? : number ; saturation ? : number ; iso ? : number ; exposureCompensation ? : number ; exposureMode ? : ExposureMode ; awbMode ? : AwbMode ; analogGain ? : number ; digitalGain ? : number ; } declare interface StreamCamera { on ( event , listener ) : this ; once ( event , listener ) : this ; } class StreamCamera extends EventEmitter { private readonly options : StreamOptions ; private childProcess ? : ChildProcessWithoutNullStreams ; private streams : Array < stream . Readable > = [ ] ; static readonly jpegSignature = Buffer . from ( [ 0xff , 0xd8 , 0xff , 0xdb , 0x00 , 0x84 , 0x00 ] ) ; constructor ( options = { } ) { super ( ) ; this . options = { rotation : Rotation . Rotate0 , flip : Flip . None , bitRate : 17000000 , fps : 30 , codec : Codec . H264 , sensorMode : SensorMode . AutoSelect , ... options , } ; } startCapture ( ) { return new Promise ( async ( resolve , reject ) => { try { const args = [ ... getSharedArgs ( this . options ) , ... ( this . options . bitRate ? [ '--bitrate' , this . options . bitRate . toString ( ) ] : [ ] ) , ... ( this . options . fps ? [ '--framerate' , this . options . fps . toString ( ) ] : [ ] ) , ... ( this . options . codec ? [ '--codec' , this . options . codec . toString ( ) ] : [ ] ) , ... ( this . options . sensorMode ? [ '--mode' , this . options . sensorMode . toString ( ) ] : [ ] ) , '--timeout' , ( 0 ) . toString ( ) , '--nopreview' , '--output' , '-' , ] ; this . childProcess = spawn ( 'raspivid' , args ) ; this . childProcess . once ( 'error' , ( ) => reject ( new Error ( "Could not start capture with StreamCamera. Are you running on a Raspberry Pi with 'raspivid' installed?" ) ) ) ; this . childProcess . stdout . once ( 'data' , ( ) => resolve ( ) ) ; let stdoutBuffer = Buffer . alloc ( 0 ) ; this . childProcess . stdout . on ( 'data' , ( data ) => { this . streams . forEach ( stream => stream . push ( data ) ) ; if ( this . options . codec !== Codec . MJPEG ) return ; stdoutBuffer = Buffer . concat ( [ stdoutBuffer , data ] ) ; while ( true ) { const signatureIndex = stdoutBuffer . indexOf ( StreamCamera . jpegSignature , 0 ) ; if ( signatureIndex === - 1 ) break ; if ( signatureIndex > 0 ) stdoutBuffer = stdoutBuffer . slice ( signatureIndex ) ; const nextSignatureIndex = stdoutBuffer . indexOf ( StreamCamera . jpegSignature , StreamCamera . jpegSignature . length ) ; if ( nextSignatureIndex === - 1 ) break ; this . emit ( 'frame' , stdoutBuffer . slice ( 0 , nextSignatureIndex ) ) ; stdoutBuffer = stdoutBuffer . slice ( nextSignatureIndex ) ; } } ) ; this . childProcess . stdout . on ( 'error' , err => this . emit ( 'error' , err ) ) ; this . childProcess . stderr . on ( 'data' , data => this . emit ( 'error' , new Error ( data . toString ( ) ) ) ) ; this . childProcess . stderr . on ( 'error' , err => this . emit ( 'error' , err ) ) ; this . childProcess . stdout . on ( 'close' , ( ) => this . emit ( 'close' ) ) ; } catch ( err ) { return reject ( err ) ; } } ) ; } async stopCapture ( ) { if ( this . childProcess ) { this . childProcess . kill ( ) ; } this . streams . forEach ( stream => stream . push ( null ) ) ; this . streams = [ ] ; } createStream ( ) { const newStream = new stream . Readable ( { read : ( ) => { } , } ) ; this . streams . push ( newStream ) ; return newStream ; } takeImage ( ) { if ( this . options . codec !== Codec . MJPEG ) throw new Error ( "Codec must be 'MJPEG' to take image" ) ; return new Promise < Buffer > ( resolve => this . once ( 'frame' , data => resolve ( data ) ) ) ; } } export default StreamCamera ;