|
- /*
- * Multipart JPEG format
- * Copyright (c) 2015 Luca Barbato
- *
- * This file is part of FFmpeg.
- *
- * FFmpeg is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * FFmpeg 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
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with FFmpeg; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
- #include "libavutil/avstring.h"
- #include "libavutil/opt.h"
-
- #include "avformat.h"
- #include "internal.h"
- #include "avio_internal.h"
-
-
-
- typedef struct MPJPEGDemuxContext {
- const AVClass *class;
- char *boundary;
- char *searchstr;
- int searchstr_len;
- int strict_mime_boundary;
- } MPJPEGDemuxContext;
-
-
- static void trim_right(char *p)
- {
- char *end;
-
- if (!p || !*p)
- return;
-
- end = p + strlen(p);
- while (end > p && av_isspace(*(end-1)))
- *(--end) = '\0';
- }
-
- static int get_line(AVIOContext *pb, char *line, int line_size)
- {
- ff_get_line(pb, line, line_size);
-
- if (pb->error)
- return pb->error;
-
- if (pb->eof_reached)
- return AVERROR_EOF;
-
- trim_right(line);
- return 0;
- }
-
-
-
- static int split_tag_value(char **tag, char **value, char *line)
- {
- char *p = line;
- int foundData = 0;
-
- *tag = NULL;
- *value = NULL;
-
-
- while (*p != '\0' && *p != ':') {
- if (!av_isspace(*p)) {
- foundData = 1;
- }
- p++;
- }
- if (*p != ':')
- return foundData ? AVERROR_INVALIDDATA : 0;
-
- *p = '\0';
- *tag = line;
- trim_right(*tag);
-
- p++;
-
- while (av_isspace(*p))
- p++;
-
- *value = p;
- trim_right(*value);
-
- return 0;
- }
-
- static int parse_multipart_header(AVIOContext *pb,
- int* size,
- const char* expected_boundary,
- void *log_ctx);
-
- static int mpjpeg_read_close(AVFormatContext *s)
- {
- MPJPEGDemuxContext *mpjpeg = s->priv_data;
- av_freep(&mpjpeg->boundary);
- av_freep(&mpjpeg->searchstr);
- return 0;
- }
-
- static int mpjpeg_read_probe(AVProbeData *p)
- {
- AVIOContext *pb;
- int ret = 0;
- int size = 0;
-
- if (p->buf_size < 2 || p->buf[0] != '-' || p->buf[1] != '-')
- return 0;
-
- pb = avio_alloc_context(p->buf, p->buf_size, 0, NULL, NULL, NULL, NULL);
- if (!pb)
- return 0;
-
- ret = (parse_multipart_header(pb, &size, "--", NULL) >= 0) ? AVPROBE_SCORE_MAX : 0;
-
- avio_context_free(&pb);
-
- return ret;
- }
-
- static int mpjpeg_read_header(AVFormatContext *s)
- {
- AVStream *st;
- char boundary[70 + 2 + 1] = {0};
- int64_t pos = avio_tell(s->pb);
- int ret;
-
- do {
- ret = get_line(s->pb, boundary, sizeof(boundary));
- if (ret < 0)
- return ret;
- } while (!boundary[0]);
-
- if (strncmp(boundary, "--", 2))
- return AVERROR_INVALIDDATA;
-
- st = avformat_new_stream(s, NULL);
- if (!st)
- return AVERROR(ENOMEM);
-
- st->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
- st->codecpar->codec_id = AV_CODEC_ID_MJPEG;
-
- avpriv_set_pts_info(st, 60, 1, 25);
-
- avio_seek(s->pb, pos, SEEK_SET);
-
- return 0;
- }
-
- static int parse_content_length(const char *value)
- {
- long int val = strtol(value, NULL, 10);
-
- if (val == LONG_MIN || val == LONG_MAX)
- return AVERROR(errno);
- if (val > INT_MAX)
- return AVERROR(ERANGE);
- return val;
- }
-
- static int parse_multipart_header(AVIOContext *pb,
- int* size,
- const char* expected_boundary,
- void *log_ctx)
- {
- char line[128];
- int found_content_type = 0;
- int ret;
-
- *size = -1;
-
- // get the CRLF as empty string
- ret = get_line(pb, line, sizeof(line));
- if (ret < 0)
- return ret;
-
- /* some implementation do not provide the required
- * initial CRLF (see rfc1341 7.2.1)
- */
- while (!line[0]) {
- ret = get_line(pb, line, sizeof(line));
- if (ret < 0)
- return ret;
- }
-
- if (!av_strstart(line, expected_boundary, NULL)) {
- if (log_ctx)
- av_log(log_ctx,
- AV_LOG_ERROR,
- "Expected boundary '%s' not found, instead found a line of %"SIZE_SPECIFIER" bytes\n",
- expected_boundary,
- strlen(line));
-
- return AVERROR_INVALIDDATA;
- }
-
- while (!pb->eof_reached) {
- char *tag, *value;
-
- ret = get_line(pb, line, sizeof(line));
- if (ret < 0) {
- if (ret == AVERROR_EOF)
- break;
- return ret;
- }
-
- if (line[0] == '\0')
- break;
-
- ret = split_tag_value(&tag, &value, line);
- if (ret < 0)
- return ret;
- if (value==NULL || tag==NULL)
- break;
-
- if (!av_strcasecmp(tag, "Content-type")) {
- if (av_strcasecmp(value, "image/jpeg")) {
- if (log_ctx)
- av_log(log_ctx, AV_LOG_ERROR,
- "Unexpected %s : %s\n",
- tag, value);
- return AVERROR_INVALIDDATA;
- } else
- found_content_type = 1;
- } else if (!av_strcasecmp(tag, "Content-Length")) {
- *size = parse_content_length(value);
- if ( *size < 0 )
- if (log_ctx)
- av_log(log_ctx, AV_LOG_WARNING,
- "Invalid Content-Length value : %s\n",
- value);
- }
- }
-
- return found_content_type ? 0 : AVERROR_INVALIDDATA;
- }
-
-
- static char* mpjpeg_get_boundary(AVIOContext* pb)
- {
- uint8_t *mime_type = NULL;
- const char *start;
- const char *end;
- uint8_t *res = NULL;
- int len;
-
- /* get MIME type, and skip to the first parameter */
- av_opt_get(pb, "mime_type", AV_OPT_SEARCH_CHILDREN, &mime_type);
- start = mime_type;
- while (start != NULL && *start != '\0') {
- start = strchr(start, ';');
- if (!start)
- break;
-
- start = start+1;
-
- while (av_isspace(*start))
- start++;
-
- if (!av_stristart(start, "boundary=", &start)) {
- end = strchr(start, ';');
- if (end)
- len = end - start - 1;
- else
- len = strlen(start);
-
- /* some endpoints may enclose the boundary
- in Content-Type in quotes */
- if ( len>2 && *start == '"' && start[len-1] == '"' ) {
- start++;
- len -= 2;
- }
- res = av_strndup(start, len);
- break;
- }
- }
-
- av_freep(&mime_type);
- return res;
- }
-
-
- static int mpjpeg_read_packet(AVFormatContext *s, AVPacket *pkt)
- {
- int size;
- int ret;
-
- MPJPEGDemuxContext *mpjpeg = s->priv_data;
- if (mpjpeg->boundary == NULL) {
- uint8_t* boundary = NULL;
- if (mpjpeg->strict_mime_boundary) {
- boundary = mpjpeg_get_boundary(s->pb);
- }
- if (boundary != NULL) {
- mpjpeg->boundary = boundary;
- mpjpeg->searchstr = av_asprintf( "\r\n%s\r\n", boundary );
- } else {
- mpjpeg->boundary = av_strdup("--");
- mpjpeg->searchstr = av_strdup("\r\n--");
- }
- if (!mpjpeg->boundary || !mpjpeg->searchstr) {
- av_freep(&mpjpeg->boundary);
- av_freep(&mpjpeg->searchstr);
- return AVERROR(ENOMEM);
- }
- mpjpeg->searchstr_len = strlen(mpjpeg->searchstr);
- }
-
- ret = parse_multipart_header(s->pb, &size, mpjpeg->boundary, s);
-
-
- if (ret < 0)
- return ret;
-
- if (size > 0) {
- /* size has been provided to us in MIME header */
- ret = av_get_packet(s->pb, pkt, size);
- } else {
- /* no size was given -- we read until the next boundary or end-of-file */
- int remaining = 0, len;
-
- const int read_chunk = 2048;
- av_init_packet(pkt);
- pkt->data = NULL;
- pkt->size = 0;
- pkt->pos = avio_tell(s->pb);
-
- /* we may need to return as much as all we've read back to the buffer */
- ffio_ensure_seekback(s->pb, read_chunk);
-
- while ((ret = av_append_packet(s->pb, pkt, read_chunk - remaining)) >= 0) {
- /* scan the new data */
- char *start;
-
- len = ret + remaining;
- start = pkt->data + pkt->size - len;
- do {
- if (!memcmp(start, mpjpeg->searchstr, mpjpeg->searchstr_len)) {
- // got the boundary! rewind the stream
- avio_seek(s->pb, -len, SEEK_CUR);
- pkt->size -= len;
- return pkt->size;
- }
- len--;
- start++;
- } while (len >= mpjpeg->searchstr_len);
- remaining = len;
- }
-
- /* error or EOF occurred */
- if (ret == AVERROR_EOF) {
- ret = pkt->size > 0 ? pkt->size : AVERROR_EOF;
- } else {
- av_packet_unref(pkt);
- }
- }
-
- return ret;
- }
-
- #define OFFSET(x) offsetof(MPJPEGDemuxContext, x)
-
- #define DEC AV_OPT_FLAG_DECODING_PARAM
- static const AVOption mpjpeg_options[] = {
- { "strict_mime_boundary", "require MIME boundaries match", OFFSET(strict_mime_boundary), AV_OPT_TYPE_BOOL, {.i64 = 0}, 0, 1, DEC },
- { NULL }
- };
-
-
- static const AVClass mpjpeg_demuxer_class = {
- .class_name = "MPJPEG demuxer",
- .item_name = av_default_item_name,
- .option = mpjpeg_options,
- .version = LIBAVUTIL_VERSION_INT,
- };
-
- AVInputFormat ff_mpjpeg_demuxer = {
- .name = "mpjpeg",
- .long_name = NULL_IF_CONFIG_SMALL("MIME multipart JPEG"),
- .mime_type = "multipart/x-mixed-replace",
- .extensions = "mjpg",
- .priv_data_size = sizeof(MPJPEGDemuxContext),
- .read_probe = mpjpeg_read_probe,
- .read_header = mpjpeg_read_header,
- .read_packet = mpjpeg_read_packet,
- .read_close = mpjpeg_read_close,
- .priv_class = &mpjpeg_demuxer_class,
- .flags = AVFMT_NOTIMESTAMPS,
- };
-
|