/* DESCRIPTION extract.go provides an Extractor to get JPEG images from an RTP/JPEG stream defined by RFC 2435 (see https://tools.ietf.org/html/rfc2435). AUTHOR Saxon Nelson-Milton LICENSE Copyright (C) 2019 the Australian Ocean Lab (AusOcean) It is free software: you can redistribute it and/or modify them under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. It is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with revid in gpl.txt. If not, see http://www.gnu.org/licenses. */ // Package mjpeg provides functionality for extraction of MJPEG from an // RTP-MJPEG stream and lexing of individual JPEGs from a bare bones // MJPEG stream. package mjpeg import ( "fmt" "io" "time" "bitbucket.org/ausocean/av/protocol/rtp" ) const maxRTPSize = 1500 // Max ethernet transmission unit in bytes. // Extractor is an Extractor for extracting JPEG from an RTP stream. type Extractor struct { dst io.Writer // The destination we'll be writing extracted JPEGs to. } // NewExtractor returns a new Extractor. func NewExtractor() *Extractor { return &Extractor{} } // Extract will continously read RTP packets from src containing JPEG (in RTP // payload format) and extract the JPEG images, sending them to dst. This // function expects that each read from src will provide a single RTP packet. func (e *Extractor) Extract(dst io.Writer, src io.Reader, delay time.Duration) error { buf := make([]byte, maxRTPSize) ctx := NewContext(dst) for { n, err := src.Read(buf) switch err { case nil: // Do nothing. case io.EOF: return nil default: return fmt.Errorf("source read error: %v\n", err) } // Get payload from RTP packet. p, err := rtp.Payload(buf[:n]) if err != nil { return fmt.Errorf("could not get RTP payload: %w\n", err) } // Also grab the marker so that we know when the JPEG is finished. m, err := rtp.Marker(buf[:n]) if err != nil { return fmt.Errorf("could not read RTP marker: %w", err) } err = ctx.ParsePayload(p, m) switch err { case nil: // Do nothing. case ErrNoFrameStart: // If no frame start then we continue until we get one. default: return fmt.Errorf("could not parse JPEG scan: %w", err) } } }