diff options
Diffstat (limited to 'common')
-rw-r--r-- | common/asxparser.c | 581 | ||||
-rw-r--r-- | common/asxparser.h | 27 | ||||
-rw-r--r-- | common/av_common.c | 178 | ||||
-rw-r--r-- | common/av_common.h | 39 | ||||
-rw-r--r-- | common/av_log.c | 176 | ||||
-rw-r--r-- | common/av_log.h | 2 | ||||
-rw-r--r-- | common/av_opts.c | 55 | ||||
-rw-r--r-- | common/av_opts.h | 30 | ||||
-rw-r--r-- | common/codecs.c | 147 | ||||
-rw-r--r-- | common/codecs.h | 43 | ||||
-rw-r--r-- | common/common.c | 163 | ||||
-rw-r--r-- | common/common.h | 81 | ||||
-rw-r--r-- | common/cpudetect.c | 56 | ||||
-rw-r--r-- | common/cpudetect.h | 40 | ||||
-rw-r--r-- | common/encode.h | 21 | ||||
-rw-r--r-- | common/encode_lavc.c | 1115 | ||||
-rw-r--r-- | common/encode_lavc.h | 101 | ||||
-rw-r--r-- | common/global.h | 12 | ||||
-rw-r--r-- | common/msg.c | 389 | ||||
-rw-r--r-- | common/msg.h | 180 | ||||
-rw-r--r-- | common/playlist.c | 241 | ||||
-rw-r--r-- | common/playlist.h | 82 | ||||
-rw-r--r-- | common/playlist_parser.c | 566 | ||||
-rw-r--r-- | common/playlist_parser.h | 29 | ||||
-rw-r--r-- | common/version.c | 26 |
25 files changed, 4380 insertions, 0 deletions
diff --git a/common/asxparser.c b/common/asxparser.c new file mode 100644 index 0000000000..5b1d5652a3 --- /dev/null +++ b/common/asxparser.c @@ -0,0 +1,581 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "config.h" + +#include <stdlib.h> +#include <stdio.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> + +#include "playlist.h" +#include "playlist_parser.h" +#include "stream/stream.h" +#include "asxparser.h" +#include "common/msg.h" + + +typedef struct ASX_Parser_t ASX_Parser_t; + +typedef struct { + char* buffer; + int line; +} ASX_LineSave_t; + +struct ASX_Parser_t { + int line; // Curent line + ASX_LineSave_t *ret_stack; + int ret_stack_size; + char* last_body; + int deep; + struct playlist *pl; +}; + +ASX_Parser_t *asx_parser_new(struct playlist *pl); + +void +asx_parser_free(ASX_Parser_t* parser); + +/* + * Return -1 on error, 0 when nothing is found, 1 on sucess + */ +int +asx_get_element(ASX_Parser_t* parser,char** _buffer, + char** _element,char** _body,char*** _attribs); + +int +asx_parse_attribs(ASX_Parser_t* parser,char* buffer,char*** _attribs); + +/////// Attribs utils + +char* +asx_get_attrib(const char* attrib,char** attribs); + +#define asx_free_attribs(a) asx_list_free(&a,free) + +////// List utils + +typedef void (*ASX_FreeFunc)(void* arg); + +void +asx_list_free(void* list_ptr,ASX_FreeFunc free_func); + + +////// List utils + +void +asx_list_free(void* list_ptr,ASX_FreeFunc free_func) { + void** ptr = *(void***)list_ptr; + if(ptr == NULL) return; + if(free_func != NULL) { + for( ; *ptr != NULL ; ptr++) + free_func(*ptr); + } + free(*(void**)list_ptr); + *(void**)list_ptr = NULL; +} + +/////// Attribs utils + +char* +asx_get_attrib(const char* attrib,char** attribs) { + char** ptr; + + if(attrib == NULL || attribs == NULL) return NULL; + for(ptr = attribs; ptr[0] != NULL; ptr += 2){ + if(strcasecmp(ptr[0],attrib) == 0) + return strdup(ptr[1]); + } + return NULL; +} + +#define asx_warning_attrib_required(p,e,a) mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : element %s don't have the required attribute %s",p->line,e,a) +#define asx_warning_body_parse_error(p,e) mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : error while parsing %s body",p->line,e) + +ASX_Parser_t *asx_parser_new(struct playlist *pl) +{ + ASX_Parser_t* parser = calloc(1,sizeof(ASX_Parser_t)); + parser->pl = pl; + return parser; +} + +void +asx_parser_free(ASX_Parser_t* parser) { + if(!parser) return; + free(parser->ret_stack); + free(parser); + +} + +#define LETTER "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +#define SPACE " \n\t\r" + +int +asx_parse_attribs(ASX_Parser_t* parser,char* buffer,char*** _attribs) { + char *ptr1, *ptr2, *ptr3; + int n_attrib = 0; + char **attribs = NULL; + char *attrib, *val; + + ptr1 = buffer; + while(1) { + for( ; strchr(SPACE,*ptr1) != NULL; ptr1++) { // Skip space + if(*ptr1 == '\0') break; + } + ptr3 = strchr(ptr1,'='); + if(ptr3 == NULL) break; + for(ptr2 = ptr3-1; strchr(SPACE,*ptr2) != NULL; ptr2--) { + if (ptr2 == ptr1) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : this should never append, back to attribute begin while skipping end space",parser->line); + break; + } + } + attrib = malloc(ptr2-ptr1+2); + strncpy(attrib,ptr1,ptr2-ptr1+1); + attrib[ptr2-ptr1+1] = '\0'; + + ptr1 = strchr(ptr3,'"'); + if(ptr1 == NULL || ptr1[1] == '\0') ptr1 = strchr(ptr3,'\''); + if(ptr1 == NULL || ptr1[1] == '\0') { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : can't find attribute %s value",parser->line,attrib); + free(attrib); + break; + } + ptr2 = strchr(ptr1+1,ptr1[0]); + if (ptr2 == NULL) { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : value of attribute %s isn't finished",parser->line,attrib); + free(attrib); + break; + } + ptr1++; + val = malloc(ptr2-ptr1+1); + strncpy(val,ptr1,ptr2-ptr1); + val[ptr2-ptr1] = '\0'; + n_attrib++; + + attribs = realloc(attribs, (2 * n_attrib + 1) * sizeof(char*)); + attribs[n_attrib*2-2] = attrib; + attribs[n_attrib*2-1] = val; + + ptr1 = ptr2+1; + } + + if(n_attrib > 0) + attribs[n_attrib*2] = NULL; + + *_attribs = attribs; + + return n_attrib; +} + +/* + * Return -1 on error, 0 when nothing is found, 1 on sucess + */ +int +asx_get_element(ASX_Parser_t* parser,char** _buffer, + char** _element,char** _body,char*** _attribs) { + char *ptr1,*ptr2, *ptr3, *ptr4; + char *attribs = NULL; + char *element = NULL, *body = NULL, *ret = NULL, *buffer; + int n_attrib = 0; + int body_line = 0,attrib_line,ret_line,in = 0; + int quotes = 0; + + if(_buffer == NULL || _element == NULL || _body == NULL || _attribs == NULL) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : asx_get_element called with invalid value",parser->line); + return -1; + } + + *_body = *_element = NULL; + *_attribs = NULL; + buffer = *_buffer; + + if(buffer == NULL) return 0; + + if(parser->ret_stack && /*parser->last_body && */buffer != parser->last_body) { + ASX_LineSave_t* ls = parser->ret_stack; + int i; + for(i = 0 ; i < parser->ret_stack_size ; i++) { + if(buffer == ls[i].buffer) { + parser->line = ls[i].line; + break; + } + + } + if( i < parser->ret_stack_size) { + i++; + if( i < parser->ret_stack_size) + memmove(parser->ret_stack,parser->ret_stack+i, (parser->ret_stack_size - i)*sizeof(ASX_LineSave_t)); + parser->ret_stack_size -= i; + if(parser->ret_stack_size > 0) + parser->ret_stack = realloc(parser->ret_stack,parser->ret_stack_size*sizeof(ASX_LineSave_t)); + else { + free(parser->ret_stack); + parser->ret_stack = NULL; + } + } + } + + ptr1 = buffer; + while(1) { + for( ; ptr1[0] != '<' ; ptr1++) { + if(ptr1[0] == '\0') { + ptr1 = NULL; + break; + } + if(ptr1[0] == '\n') parser->line++; + } + //ptr1 = strchr(ptr1,'<'); + if(!ptr1 || ptr1[1] == '\0') return 0; // Nothing found + + if(strncmp(ptr1,"<!--",4) == 0) { // Comments + for( ; strncmp(ptr1,"-->",3) != 0 ; ptr1++) { + if(ptr1[0] == '\0') { + ptr1 = NULL; + break; + } + if(ptr1[0] == '\n') parser->line++; + } + //ptr1 = strstr(ptr1,"-->"); + if(!ptr1) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : unfinished comment",parser->line); + return -1; + } + } else { + break; + } + } + + // Is this space skip very useful ?? + for(ptr1++; strchr(SPACE,ptr1[0]) != NULL; ptr1++) { // Skip space + if(ptr1[0] == '\0') { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line); + return -1; + } + if(ptr1[0] == '\n') parser->line++; + } + + for(ptr2 = ptr1; strchr(LETTER,*ptr2) != NULL;ptr2++) { // Go to end of name + if(*ptr2 == '\0'){ + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line); + return -1; + } + if(ptr2[0] == '\n') parser->line++; + } + + element = malloc(ptr2-ptr1+1); + strncpy(element,ptr1,ptr2-ptr1); + element[ptr2-ptr1] = '\0'; + + for( ; strchr(SPACE,*ptr2) != NULL; ptr2++) { // Skip space + if(ptr2[0] == '\0') { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line); + free(element); + return -1; + } + if(ptr2[0] == '\n') parser->line++; + } + attrib_line = parser->line; + + + + for(ptr3 = ptr2; ptr3[0] != '\0'; ptr3++) { // Go to element end + if(ptr3[0] == '"') quotes ^= 1; + if(!quotes && (ptr3[0] == '>' || strncmp(ptr3,"/>",2) == 0)) + break; + if(ptr3[0] == '\n') parser->line++; + } + if(ptr3[0] == '\0' || ptr3[1] == '\0') { // End of file + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing element start",parser->line); + free(element); + return -1; + } + + // Save attribs string + if(ptr3-ptr2 > 0) { + attribs = malloc(ptr3-ptr2+1); + strncpy(attribs,ptr2,ptr3-ptr2); + attribs[ptr3-ptr2] = '\0'; + } + //bs_line = parser->line; + if(ptr3[0] != '/') { // Not Self closed element + ptr3++; + for( ; strchr(SPACE,*ptr3) != NULL; ptr3++) { // Skip space on body begin + if(*ptr3 == '\0') { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing %s element body",parser->line,element); + free(element); + free(attribs); + return -1; + } + if(ptr3[0] == '\n') parser->line++; + } + ptr4 = ptr3; + body_line = parser->line; + while(1) { // Find closing element + for( ; ptr4[0] != '<' ; ptr4++) { + if(ptr4[0] == '\0') { + ptr4 = NULL; + break; + } + if(ptr4[0] == '\n') parser->line++; + } + if(ptr4 && strncmp(ptr4,"<!--",4) == 0) { // Comments + for( ; strncmp(ptr4,"-->",3) != 0 ; ptr4++) { + if(ptr4[0] == '\0') { + ptr4 = NULL; + break; + } + if(ptr1[0] == '\n') parser->line++; + } + continue; + } + if(ptr4 == NULL || ptr4[1] == '\0') { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : EOB reached while parsing %s element body",parser->line,element); + free(element); + free(attribs); + return -1; + } + if(ptr4[1] != '/' && strncasecmp(element,ptr4+1,strlen(element)) == 0) { + in++; + ptr4+=2; + continue; + } else if(strncasecmp(element,ptr4+2,strlen(element)) == 0) { // Extract body + if(in > 0) { + in--; + ptr4 += 2+strlen(element); + continue; + } + ret = ptr4+strlen(element)+3; + if(ptr4 != ptr3) { + ptr4--; + for( ; ptr4 != ptr3 && strchr(SPACE,*ptr4) != NULL; ptr4--) ;// Skip space on body end + // if(ptr4[0] == '\0') parser->line--; + //} + ptr4++; + body = malloc(ptr4-ptr3+1); + strncpy(body,ptr3,ptr4-ptr3); + body[ptr4-ptr3] = '\0'; + } + break; + } else { + ptr4 += 2; + } + } + } else { + ret = ptr3 + 2; // 2 is for /> + } + + for( ; ret[0] != '\0' && strchr(SPACE,ret[0]) != NULL; ret++) { // Skip space + if(ret[0] == '\n') parser->line++; + } + + ret_line = parser->line; + + if(attribs) { + parser->line = attrib_line; + n_attrib = asx_parse_attribs(parser,attribs,_attribs); + free(attribs); + if(n_attrib < 0) { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"At line %d : error while parsing element %s attributes",parser->line,element); + free(element); + free(body); + return -1; + } + } else + *_attribs = NULL; + + *_element = element; + *_body = body; + + parser->last_body = body; + parser->ret_stack_size++; + parser->ret_stack = realloc(parser->ret_stack,parser->ret_stack_size*sizeof(ASX_LineSave_t)); + if(parser->ret_stack_size > 1) + memmove(parser->ret_stack+1,parser->ret_stack,(parser->ret_stack_size-1)*sizeof(ASX_LineSave_t)); + parser->ret_stack[0].buffer = ret; + parser->ret_stack[0].line = ret_line; + parser->line = body ? body_line : ret_line; + + *_buffer = ret; + return 1; + +} + +static void +asx_parse_ref(ASX_Parser_t* parser, char** attribs) { + char *href; + + href = asx_get_attrib("HREF",attribs); + if(href == NULL) { + asx_warning_attrib_required(parser,"REF" ,"HREF" ); + return; + } +#if 0 + // replace http my mmshttp to avoid infinite loops + // disabled since some playlists for e.g. WinAMP use asx as well + // "-user-agent NSPlayer/4.1.0.3856" is a possible workaround + if (strncmp(href, "http://", 7) == 0) { + char *newref = malloc(3 + strlen(href) + 1); + strcpy(newref, "mms"); + strcpy(newref + 3, href); + free(href); + href = newref; + } +#endif + + playlist_add_file(parser->pl, href); + + mp_msg(MSGT_PLAYTREE,MSGL_V,"Adding file %s to element entry\n",href); + + free(href); + +} + +static void asx_parse_entryref(ASX_Parser_t* parser,char* buffer,char** _attribs) { + char *href; + + if(parser->deep > 0) + return; + + href = asx_get_attrib("HREF",_attribs); + if(href == NULL) { + asx_warning_attrib_required(parser,"ENTRYREF" ,"HREF" ); + return; + } + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Recursive playlist %s\n", href); + playlist_add_file(parser->pl, href); + free(href); + //mp_msg(MSGT_PLAYTREE,MSGL_INFO,"Need to implement entryref\n"); +} + +static void asx_parse_entry(ASX_Parser_t* parser,char* buffer,char** _attribs) { + char *element,*body,**attribs; + int r; + + while(buffer && buffer[0] != '\0') { + r = asx_get_element(parser,&buffer,&element,&body,&attribs); + if(r < 0) { + asx_warning_body_parse_error(parser,"ENTRY"); + return; + } else if (r == 0) { // No more element + break; + } + if(strcasecmp(element,"REF") == 0) { + asx_parse_ref(parser,attribs); + mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Adding element %s to entry\n",element); + } else + mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Ignoring element %s\n",element); + free(body); + asx_free_attribs(attribs); + } + +} + + +static void asx_parse_repeat(ASX_Parser_t* parser,char* buffer,char** _attribs) { + char *element,*body,**attribs; + int r; + + asx_get_attrib("COUNT",_attribs); + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"Ignoring repeated playlist entries\n"); + + while(buffer && buffer[0] != '\0') { + r = asx_get_element(parser,&buffer,&element,&body,&attribs); + if(r < 0) { + asx_warning_body_parse_error(parser,"REPEAT"); + return; + } else if (r == 0) { // No more element + break; + } + if(strcasecmp(element,"ENTRY") == 0) { + asx_parse_entry(parser,body,attribs); + } else if(strcasecmp(element,"ENTRYREF") == 0) { + asx_parse_entryref(parser,body,attribs); + } else if(strcasecmp(element,"REPEAT") == 0) { + asx_parse_repeat(parser,body,attribs); + } else + mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Ignoring element %s\n",element); + free(body); + asx_free_attribs(attribs); + } + +} + + +bool asx_parse(char* buffer, struct playlist *pl) +{ + char *element,*asx_body,**asx_attribs,*body = NULL, **attribs; + int r; + ASX_Parser_t* parser = asx_parser_new(pl); + + parser->line = 1; + parser->deep = 0; + + r = asx_get_element(parser,&buffer,&element,&asx_body,&asx_attribs); + if(r < 0) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"At line %d : Syntax error ???",parser->line); + asx_parser_free(parser); + return false; + } else if(r == 0) { // No contents + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"empty asx element"); + asx_parser_free(parser); + return false; + } + + if(strcasecmp(element,"ASX") != 0) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"first element isn't ASX, it's %s\n",element); + asx_free_attribs(asx_attribs); + asx_parser_free(parser); + return false; + } + + if(!asx_body) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR,"ASX element is empty"); + asx_free_attribs(asx_attribs); + asx_parser_free(parser); + return false; + } + + buffer = asx_body; + while(buffer && buffer[0] != '\0') { + r = asx_get_element(parser,&buffer,&element,&body,&attribs); + if(r < 0) { + asx_warning_body_parse_error(parser,"ASX"); + asx_parser_free(parser); + return false; + } else if (r == 0) { // No more element + break; + } + if(strcasecmp(element,"ENTRY") == 0) { + asx_parse_entry(parser,body,attribs); + } else if(strcasecmp(element,"ENTRYREF") == 0) { + asx_parse_entryref(parser,body,attribs); + } else if(strcasecmp(element,"REPEAT") == 0) { + asx_parse_repeat(parser,body,attribs); + } else + mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"Ignoring element %s\n",element); + free(body); + asx_free_attribs(attribs); + } + + free(asx_body); + asx_free_attribs(asx_attribs); + asx_parser_free(parser); + return true; +} diff --git a/common/asxparser.h b/common/asxparser.h new file mode 100644 index 0000000000..e49a2cedc0 --- /dev/null +++ b/common/asxparser.h @@ -0,0 +1,27 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_ASXPARSER_H +#define MPLAYER_ASXPARSER_H + +#include <stdbool.h> + +struct playlist; +bool asx_parse(char* buffer, struct playlist *pl); + +#endif /* MPLAYER_ASXPARSER_H */ diff --git a/common/av_common.c b/common/av_common.c new file mode 100644 index 0000000000..cccb0f755e --- /dev/null +++ b/common/av_common.c @@ -0,0 +1,178 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> + +#include <libavutil/common.h> +#include <libavcodec/avcodec.h> + +#include "common/common.h" +#include "common/msg.h" +#include "demux/packet.h" +#include "av_common.h" +#include "codecs.h" + +#include "osdep/numcores.h" + + +// Copy the codec-related fields from st into avctx. This does not set the +// codec itself, only codec related header data provided by libavformat. +// The goal is to initialize a new decoder with the header data provided by +// libavformat, and unlike avcodec_copy_context(), allow the user to create +// a clean AVCodecContext for a manually selected AVCodec. +// This is strictly for decoding only. +void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st) +{ + if (st->extradata_size) { + av_free(avctx->extradata); + avctx->extradata_size = 0; + avctx->extradata = + av_mallocz(st->extradata_size + FF_INPUT_BUFFER_PADDING_SIZE); + if (avctx->extradata) { + avctx->extradata_size = st->extradata_size; + memcpy(avctx->extradata, st->extradata, st->extradata_size); + } + } + avctx->codec_tag = st->codec_tag; + avctx->stream_codec_tag = st->stream_codec_tag; + avctx->bit_rate = st->bit_rate; + avctx->width = st->width; + avctx->height = st->height; + avctx->pix_fmt = st->pix_fmt; + avctx->sample_aspect_ratio = st->sample_aspect_ratio; + avctx->chroma_sample_location = st->chroma_sample_location; + avctx->sample_rate = st->sample_rate; + avctx->channels = st->channels; + avctx->block_align = st->block_align; + avctx->channel_layout = st->channel_layout; + avctx->audio_service_type = st->audio_service_type; + avctx->bits_per_coded_sample = st->bits_per_coded_sample; +} + +// We merely pass-through our PTS/DTS as an int64_t; libavcodec won't use it. +union pts { int64_t i; double d; }; + +// Convert the mpv style timestamp (seconds as double) to a libavcodec style +// timestamp (integer units in a given timebase). +// +// If the given timebase is NULL or invalid, pass through the mpv timestamp by +// reinterpret casting them to int64_t. In this case, the timestamps will be +// non-sense for libavcodec, but we expect that it doesn't interpret them, +// and treats them as opaque. +int64_t mp_pts_to_av(double mp_pts, AVRational *tb) +{ + assert(sizeof(int64_t) >= sizeof(double)); + if (tb && tb->num > 0 && tb->den > 0) + return mp_pts == MP_NOPTS_VALUE ? AV_NOPTS_VALUE : mp_pts / av_q2d(*tb); + // The + 0.0 is to squash possible negative zero mp_pts, which would + // happen to end up as AV_NOPTS_VALUE. + return (union pts){.d = mp_pts + 0.0}.i; +} + +// Inverse of mp_pts_to_av(). (The timebases must be exactly the same.) +double mp_pts_from_av(int64_t av_pts, AVRational *tb) +{ + assert(sizeof(int64_t) >= sizeof(double)); + if (tb && tb->num > 0 && tb->den > 0) + return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : av_pts * av_q2d(*tb); + // Should libavcodec set the PTS to AV_NOPTS_VALUE, it would end up as + // non-sense (usually negative zero) when unwrapped to double. + return av_pts == AV_NOPTS_VALUE ? MP_NOPTS_VALUE : (union pts){.i = av_pts}.d; +} + +// Set dst from mpkt. Note that dst is not refcountable. +// mpkt can be NULL to generate empty packets (used to flush delayed data). +// Sets pts/dts using mp_pts_to_av(ts, tb). (Be aware of the implications.) +// Set duration field only if tb is set. +void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb) +{ + av_init_packet(dst); + dst->data = mpkt ? mpkt->buffer : NULL; + dst->size = mpkt ? mpkt->len : 0; + /* Some codecs (ZeroCodec, some cases of PNG) may want keyframe info + * from demuxer. */ + if (mpkt && mpkt->keyframe) + dst->flags |= AV_PKT_FLAG_KEY; + if (mpkt && mpkt->avpacket) { + dst->side_data = mpkt->avpacket->side_data; + dst->side_data_elems = mpkt->avpacket->side_data_elems; + } + if (mpkt && tb && tb->num > 0 && tb->den > 0) + dst->duration = mpkt->duration / av_q2d(*tb); + dst->pts = mp_pts_to_av(mpkt ? mpkt->pts : MP_NOPTS_VALUE, tb); + dst->dts = mp_pts_to_av(mpkt ? mpkt->dts : MP_NOPTS_VALUE, tb); +} + +void mp_set_avcodec_threads(AVCodecContext *avctx, int threads) +{ + if (threads == 0) { + threads = default_thread_count(); + if (threads < 1) { + mp_msg(MSGT_GLOBAL, MSGL_WARN, "Could not determine " + "thread count to use, defaulting to 1.\n"); + threads = 1; + } + // Apparently some libavcodec versions have or had trouble with more + // than 16 threads, and/or print a warning when using > 16. + threads = MPMIN(threads, 16); + } + avctx->thread_count = threads; +} + +void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type) +{ + AVCodec *cur = NULL; + for (;;) { + cur = av_codec_next(cur); + if (!cur) + break; + if (av_codec_is_decoder(cur) && cur->type == type) { + mp_add_decoder(list, "lavc", mp_codec_from_av_codec_id(cur->id), + cur->name, cur->long_name); + } + } +} + +int mp_codec_to_av_codec_id(const char *codec) +{ + int id = AV_CODEC_ID_NONE; + if (codec) { + const AVCodecDescriptor *desc = avcodec_descriptor_get_by_name(codec); + if (desc) + id = desc->id; + if (id == AV_CODEC_ID_NONE) { + AVCodec *avcodec = avcodec_find_decoder_by_name(codec); + if (avcodec) + id = avcodec->id; + } + } + return id; +} + +const char *mp_codec_from_av_codec_id(int codec_id) +{ + const char *name = NULL; + const AVCodecDescriptor *desc = avcodec_descriptor_get(codec_id); + if (desc) + name = desc->name; + if (!name) { + AVCodec *avcodec = avcodec_find_decoder(codec_id); + if (avcodec) + name = avcodec->name; + } + return name; +} diff --git a/common/av_common.h b/common/av_common.h new file mode 100644 index 0000000000..7bf2d64d9e --- /dev/null +++ b/common/av_common.h @@ -0,0 +1,39 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MP_AVCOMMON_H +#define MP_AVCOMMON_H + +#include <inttypes.h> + +#include <libavutil/avutil.h> +#include <libavutil/rational.h> +#include <libavcodec/avcodec.h> + +struct mp_decoder_list; +struct demux_packet; + +void mp_copy_lav_codec_headers(AVCodecContext *avctx, AVCodecContext *st); +void mp_set_av_packet(AVPacket *dst, struct demux_packet *mpkt, AVRational *tb); +int64_t mp_pts_to_av(double mp_pts, AVRational *tb); +double mp_pts_from_av(int64_t av_pts, AVRational *tb); +void mp_set_avcodec_threads(AVCodecContext *avctx, int threads); +void mp_add_lavc_decoders(struct mp_decoder_list *list, enum AVMediaType type); +int mp_codec_to_av_codec_id(const char *codec); +const char *mp_codec_from_av_codec_id(int codec_id); + +#endif diff --git a/common/av_log.c b/common/av_log.c new file mode 100644 index 0000000000..ba47500572 --- /dev/null +++ b/common/av_log.c @@ -0,0 +1,176 @@ +/* + * av_log to mp_msg converter + * Copyright (C) 2006 Michael Niedermayer + * Copyright (C) 2009 Uoti Urpala + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <stdbool.h> + +#include "av_log.h" +#include "config.h" +#include "common/msg.h" +#include <libavutil/avutil.h> +#include <libavutil/log.h> + +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libswscale/swscale.h> + +#if HAVE_LIBAVDEVICE +#include <libavdevice/avdevice.h> +#endif + +#if HAVE_LIBAVFILTER +#include <libavfilter/avfilter.h> +#endif + +#if HAVE_LIBAVRESAMPLE +#include <libavresample/avresample.h> +#endif +#if HAVE_LIBSWRESAMPLE +#include <libswresample/swresample.h> +#endif + +static int av_log_level_to_mp_level(int av_level) +{ + if (av_level > AV_LOG_VERBOSE) + return MSGL_DBG2; + if (av_level > AV_LOG_INFO) + return MSGL_V; + if (av_level > AV_LOG_WARNING) + return MSGL_V; + if (av_level > AV_LOG_ERROR) + return MSGL_WARN; + if (av_level > AV_LOG_FATAL) + return MSGL_ERR; + return MSGL_FATAL; +} + +static int extract_msg_type_from_ctx(void *ptr) +{ + if (!ptr) + return MSGT_FIXME; + + AVClass *avc = *(AVClass **)ptr; + if (!avc) { + mp_msg(MSGT_FIXME, MSGL_WARN, + "av_log callback called with bad parameters (NULL AVClass).\n" + "This is a bug in one of Libav/FFmpeg libraries used.\n"); + return MSGT_FIXME; + } + + if (!strcmp(avc->class_name, "AVCodecContext")) { + AVCodecContext *s = ptr; + if (s->codec) { + if (s->codec->type == AVMEDIA_TYPE_AUDIO) { + if (s->codec->decode) + return MSGT_DECAUDIO; + } else if (s->codec->type == AVMEDIA_TYPE_VIDEO) { + if (s->codec->decode) + return MSGT_DECVIDEO; + } + // FIXME subtitles, encoders + // What msgt for them? There is nothing appropriate... + } + return MSGT_FIXME; + } + + if (!strcmp(avc->class_name, "AVFormatContext")) { + AVFormatContext *s = ptr; + if (s->iformat) + return MSGT_DEMUXER; + else if (s->oformat) + return MSGT_MUXER; + return MSGT_FIXME; + } + + return MSGT_FIXME; +} + +#if LIBAVCODEC_VERSION_MICRO >= 100 +#define LIB_PREFIX "ffmpeg" +#else +#define LIB_PREFIX "libav" +#endif + +static bool print_prefix = true; + +static void mp_msg_av_log_callback(void *ptr, int level, const char *fmt, + va_list vl) +{ + AVClass *avc = ptr ? *(AVClass **)ptr : NULL; + int mp_level = av_log_level_to_mp_level(level); + int type = extract_msg_type_from_ctx(ptr); + + if (!mp_msg_test(type, mp_level)) + return; + + if (print_prefix) { + mp_msg(type, mp_level, "[%s/%s] ", LIB_PREFIX, + avc ? avc->item_name(ptr) : "?"); + } + print_prefix = fmt[strlen(fmt) - 1] == '\n'; + + mp_msg_va(type, mp_level, fmt, vl); +} + +void init_libav(void) +{ + av_log_set_callback(mp_msg_av_log_callback); + avcodec_register_all(); + av_register_all(); + avformat_network_init(); + +#if HAVE_LIBAVFILTER + avfilter_register_all(); +#endif +#if HAVE_LIBAVDEVICE + avdevice_register_all(); +#endif +} + +#define V(x) (x)>>16, (x)>>8 & 255, (x) & 255 +static void print_version(int v, char *name, unsigned buildv, unsigned runv) +{ + mp_msg(MSGT_CPLAYER, v, " %-15s %d.%d.%d", name, V(buildv)); + if (buildv != runv) + mp_msg(MSGT_CPLAYER, v, " (runtime %d.%d.%d)", V(runv)); + mp_msg(MSGT_CPLAYER, v, "\n"); +} +#undef V + +void print_libav_versions(int v) +{ + mp_msg(MSGT_CPLAYER, v, "%s library versions:\n", LIB_PREFIX); + + print_version(v, "libavutil", LIBAVUTIL_VERSION_INT, avutil_version()); + print_version(v, "libavcodec", LIBAVCODEC_VERSION_INT, avcodec_version()); + print_version(v, "libavformat", LIBAVFORMAT_VERSION_INT, avformat_version()); + print_version(v, "libswscale", LIBSWSCALE_VERSION_INT, swscale_version()); +#if HAVE_LIBAVFILTER + print_version(v, "libavfilter", LIBAVFILTER_VERSION_INT, avfilter_version()); +#endif +#if HAVE_LIBAVRESAMPLE + print_version(v, "libavresample", LIBAVRESAMPLE_VERSION_INT, avresample_version()); +#endif +#if HAVE_LIBSWRESAMPLE + print_version(v, "libswresample", LIBSWRESAMPLE_VERSION_INT, swresample_version()); +#endif +} diff --git a/common/av_log.h b/common/av_log.h new file mode 100644 index 0000000000..d5c57b0aeb --- /dev/null +++ b/common/av_log.h @@ -0,0 +1,2 @@ +void init_libav(void); +void print_libav_versions(int v); diff --git a/common/av_opts.c b/common/av_opts.c new file mode 100644 index 0000000000..777a1eec5a --- /dev/null +++ b/common/av_opts.c @@ -0,0 +1,55 @@ +/* + * AVOption parsing helper + * Copyright (C) 2008 Michael Niedermayer + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdlib.h> +#include <string.h> + +#include <libavutil/opt.h> + +#include "av_opts.h" + +int parse_avopts(void *v, char *str){ + char *start; + + if (!str) + return 0; + + start= str= strdup(str); + + while(str && *str){ + char *next_opt, *arg; + + next_opt= strchr(str, ','); + if(next_opt) *next_opt++= 0; + + arg = strchr(str, '='); + if(arg) *arg++= 0; + + if (av_opt_set(v, str, arg, AV_OPT_SEARCH_CHILDREN) < 0) { + free(start); + return -1; + } + str= next_opt; + } + + free(start); + return 0; +} diff --git a/common/av_opts.h b/common/av_opts.h new file mode 100644 index 0000000000..640443a352 --- /dev/null +++ b/common/av_opts.h @@ -0,0 +1,30 @@ +/* + * AVOption parsing helper + * Copyright (C) 2008 Michael Niedermayer + * + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_AV_OPTS_H +#define MPLAYER_AV_OPTS_H + +/** + * Parses str and sets AVOptions in v accordingly. + */ +int parse_avopts(void *v, char *str); + +#endif /* MPLAYER_AV_OPTS_H */ diff --git a/common/codecs.c b/common/codecs.c new file mode 100644 index 0000000000..b7639a4576 --- /dev/null +++ b/common/codecs.c @@ -0,0 +1,147 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include "talloc.h" +#include "bstr/bstr.h" +#include "common/msg.h" +#include "codecs.h" + +void mp_add_decoder(struct mp_decoder_list *list, const char *family, + const char *codec, const char *decoder, const char *desc) +{ + struct mp_decoder_entry entry = { + .family = talloc_strdup(list, family), + .codec = talloc_strdup(list, codec), + .decoder = talloc_strdup(list, decoder), + .desc = talloc_strdup(list, desc), + }; + MP_TARRAY_APPEND(list, list->entries, list->num_entries, entry); +} + +static void mp_add_decoder_entry(struct mp_decoder_list *list, + struct mp_decoder_entry *entry) +{ + mp_add_decoder(list, entry->family, entry->codec, entry->decoder, + entry->desc); +} + +static struct mp_decoder_entry *find_decoder(struct mp_decoder_list *list, + bstr family, bstr decoder) +{ + for (int n = 0; n < list->num_entries; n++) { + struct mp_decoder_entry *cur = &list->entries[n]; + if (bstr_equals0(decoder, cur->decoder) && + bstr_equals0(family, cur->family)) + return cur; + } + return NULL; +} + +// Add entry, but only if it's not yet on the list, and if the codec matches. +// If codec == NULL, don't compare codecs. +static void add_new(struct mp_decoder_list *to, struct mp_decoder_entry *entry, + const char *codec) +{ + if (!entry || (codec && strcmp(entry->codec, codec) != 0)) + return; + if (!find_decoder(to, bstr0(entry->family), bstr0(entry->decoder))) + mp_add_decoder_entry(to, entry); +} + +// Select a decoder from the given list for the given codec. The selection +// can be influenced by the selection string, which can specify a priority +// list of preferred decoders. +// This returns a list of decoders to try, with the preferred decoders first. +// The selection string corresponds to --vd/--ad directly, and has the +// following syntax: +// selection = [<entry> ("," <entry>)*] +// entry = <family> ":" <decoder> // prefer decoder +// entry = <family> ":*" // prefer all decoders +// entry = "+" <family> ":" <decoder> // force a decoder +// entry = "-" <family> ":" <decoder> // exclude a decoder +// entry = "-" // don't add fallback decoders +// Forcing a decoder means it's added even if the codec mismatches. +struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all, + const char *codec, + const char *selection) +{ + struct mp_decoder_list *list = talloc_zero(NULL, struct mp_decoder_list); + struct mp_decoder_list *remove = talloc_zero(NULL, struct mp_decoder_list); + if (!codec) + codec = "unknown"; + bool stop = false; + bstr sel = bstr0(selection); + while (sel.len) { + bstr entry; + bstr_split_tok(sel, ",", &entry, &sel); + if (bstr_equals0(entry, "-")) { + stop = true; + break; + } + bool force = bstr_eatstart0(&entry, "+"); + bool exclude = !force && bstr_eatstart0(&entry, "-"); + struct mp_decoder_list *dest = exclude ? remove : list; + bstr family, decoder; + if (!bstr_split_tok(entry, ":", &family, &decoder)) { + mp_msg(MSGT_CFGPARSER, MSGL_ERR, "Decoders must be specified as " + "'family:decoder' for the --ad/--vd options.\n"); + break; + } + if (bstr_equals0(decoder, "*")) { + for (int n = 0; n < all->num_entries; n++) { + struct mp_decoder_entry *cur = &all->entries[n]; + if (bstr_equals0(family, cur->family)) + add_new(dest, cur, codec); + } + } else { + add_new(dest, find_decoder(all, family, decoder), + force ? NULL : codec); + } + } + if (!stop) { + // Add the remaining codecs which haven't been added yet + for (int n = 0; n < all->num_entries; n++) + add_new(list, &all->entries[n], codec); + } + for (int n = 0; n < remove->num_entries; n++) { + struct mp_decoder_entry *ex = &remove->entries[n]; + struct mp_decoder_entry *del = + find_decoder(list, bstr0(ex->family), bstr0(ex->decoder)); + if (del) { + int index = del - &list->entries[0]; + MP_TARRAY_REMOVE_AT(list->entries, list->num_entries, index); + } + } + talloc_free(remove); + return list; +} + +void mp_print_decoders(int msgt, int msgl, const char *header, + struct mp_decoder_list *list) +{ + mp_msg(msgt, msgl, "%s\n", header); + for (int n = 0; n < list->num_entries; n++) { + struct mp_decoder_entry *entry = &list->entries[n]; + mp_msg(msgt, msgl, " %s:%s", entry->family, entry->decoder); + if (strcmp(entry->decoder, entry->codec) != 0) + mp_msg(msgt, msgl, " (%s)", entry->codec); + mp_msg(msgt, msgl, " - %s\n", entry->desc); + } + if (list->num_entries == 0) + mp_msg(msgt, msgl, " (no decoders)\n"); +} diff --git a/common/codecs.h b/common/codecs.h new file mode 100644 index 0000000000..21ff284617 --- /dev/null +++ b/common/codecs.h @@ -0,0 +1,43 @@ +/* + * This file is part of mpv. + * + * mpv is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mpv 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 mpv. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MP_CODECS_H +#define MP_CODECS_H + +struct mp_decoder_entry { + const char *family; // decoder module (e.g. ad_lavc => "lavc") + const char *codec; // name of the codec (e.g. "mp3") + const char *decoder; // decoder name (e.g. "mp3float") + const char *desc; // human readable description +}; + +struct mp_decoder_list { + struct mp_decoder_entry *entries; + int num_entries; +}; + +void mp_add_decoder(struct mp_decoder_list *list, const char *family, + const char *codec, const char *decoder, const char *desc); + +struct mp_decoder_list *mp_select_decoders(struct mp_decoder_list *all, + const char *codec, + const char *selection); + +void mp_print_decoders(int msgt, int msgl, const char *header, + struct mp_decoder_list *list); + +#endif diff --git a/common/common.c b/common/common.c new file mode 100644 index 0000000000..365a369425 --- /dev/null +++ b/common/common.c @@ -0,0 +1,163 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <libavutil/common.h> + +#include "talloc.h" +#include "bstr/bstr.h" +#include "common/common.h" + +#define appendf(ptr, ...) \ + do {(*(ptr)) = talloc_asprintf_append_buffer(*(ptr), __VA_ARGS__);} while(0) + +// Return a talloc'ed string formatted according to the format string in fmt. +// On error, return NULL. +// Valid formats: +// %H, %h: hour (%H is padded with 0 to two digits) +// %M: minutes from 00-59 (hours are subtracted) +// %m: total minutes (includes hours, unlike %M) +// %S: seconds from 00-59 (minutes and hours are subtracted) +// %s: total seconds (includes hours and minutes) +// %f: like %s, but as float +// %T: milliseconds (000-999) +char *mp_format_time_fmt(const char *fmt, double time) +{ + if (time == MP_NOPTS_VALUE) + return talloc_strdup(NULL, "unknown"); + char *sign = time < 0 ? "-" : ""; + time = time < 0 ? -time : time; + long long int itime = time; + long long int h, m, tm, s; + int ms; + s = itime; + tm = s / 60; + h = s / 3600; + s -= h * 3600; + m = s / 60; + s -= m * 60; + ms = (time - itime) * 1000; + char *res = talloc_strdup(NULL, ""); + while (*fmt) { + if (fmt[0] == '%') { + fmt++; + switch (fmt[0]) { + case 'h': appendf(&res, "%s%lld", sign, h); break; + case 'H': appendf(&res, "%s%02lld", sign, h); break; + case 'm': appendf(&res, "%s%lld", sign, tm); break; + case 'M': appendf(&res, "%02lld", m); break; + case 's': appendf(&res, "%s%lld", sign, itime); break; + case 'S': appendf(&res, "%02lld", s); break; + case 'T': appendf(&res, "%03d", ms); break; + case '%': appendf(&res, "%s", "%"); break; + default: goto error; + } + fmt++; + } else { + appendf(&res, "%c", *fmt); + fmt++; + } + } + return res; +error: + talloc_free(res); + return NULL; +} + +char *mp_format_time(double time, bool fractions) +{ + return mp_format_time_fmt(fractions ? "%H:%M:%S.%T" : "%H:%M:%S", time); +} + +// Set rc to the union of rc and rc2 +void mp_rect_union(struct mp_rect *rc, const struct mp_rect *rc2) +{ + rc->x0 = FFMIN(rc->x0, rc2->x0); + rc->y0 = FFMIN(rc->y0, rc2->y0); + rc->x1 = FFMAX(rc->x1, rc2->x1); + rc->y1 = FFMAX(rc->y1, rc2->y1); +} + +// Set rc to the intersection of rc and src. +// Return false if the result is empty. +bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2) +{ + rc->x0 = FFMAX(rc->x0, rc2->x0); + rc->y0 = FFMAX(rc->y0, rc2->y0); + rc->x1 = FFMIN(rc->x1, rc2->x1); + rc->y1 = FFMIN(rc->y1, rc2->y1); + + return rc->x1 > rc->x0 && rc->y1 > rc->y0; +} + +// Encode the unicode codepoint as UTF-8, and append to the end of the +// talloc'ed buffer. +char *mp_append_utf8_buffer(char *buffer, uint32_t codepoint) +{ + char data[8]; + uint8_t tmp; + char *output = data; + PUT_UTF8(codepoint, tmp, *output++ = tmp;); + return talloc_strndup_append_buffer(buffer, data, output - data); +} + +// Parse a C-style escape beginning at code, and append the result to *str +// using talloc. The input string (*code) must point to the first character +// after the initial '\', and after parsing *code is set to the first character +// after the current escape. +// On error, false is returned, and all input remains unchanged. +bool mp_parse_escape(bstr *code, char **str) +{ + if (code->len < 1) + return false; + char replace = 0; + switch (code->start[0]) { + case '"': replace = '"'; break; + case '\\': replace = '\\'; break; + case 'b': replace = '\b'; break; + case 'f': replace = '\f'; break; + case 'n': replace = '\n'; break; + case 'r': replace = '\r'; break; + case 't': replace = '\t'; break; + case 'e': replace = '\x1b'; break; + case '\'': replace = '\''; break; + } + if (replace) { + *str = talloc_strndup_append_buffer(*str, &replace, 1); + *code = bstr_cut(*code, 1); + return true; + } + if (code->start[0] == 'x' && code->len >= 3) { + bstr num = bstr_splice(*code, 1, 3); + char c = bstrtoll(num, &num, 16); + if (!num.len) + return false; + *str = talloc_strndup_append_buffer(*str, &c, 1); + *code = bstr_cut(*code, 3); + return true; + } + if (code->start[0] == 'u' && code->len >= 5) { + bstr num = bstr_splice(*code, 1, 5); + int c = bstrtoll(num, &num, 16); + if (num.len) + return false; + *str = mp_append_utf8_buffer(*str, c); + *code = bstr_cut(*code, 5); + return true; + } + return false; +} diff --git a/common/common.h b/common/common.h new file mode 100644 index 0000000000..9e751ee851 --- /dev/null +++ b/common/common.h @@ -0,0 +1,81 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_MPCOMMON_H +#define MPLAYER_MPCOMMON_H + +#include <stdlib.h> +#include <stdbool.h> +#include <stdint.h> + +#include "compat/compiler.h" +#include "talloc.h" + +// both int64_t and double should be able to represent this exactly +#define MP_NOPTS_VALUE (-1LL<<63) + +#define MP_CONCAT_(a, b) a ## b +#define MP_CONCAT(a, b) MP_CONCAT_(a, b) + +#define ROUND(x) ((int)((x) < 0 ? (x) - 0.5 : (x) + 0.5)) + +#define MPMAX(a, b) ((a) > (b) ? (a) : (b)) +#define MPMIN(a, b) ((a) > (b) ? (b) : (a)) +#define MPCLAMP(a, min, max) (((a) < (min)) ? (min) : (((a) > (max)) ? (max) : (a))) +#define MPSWAP(type, a, b) \ + do { type SWAP_tmp = b; b = a; a = SWAP_tmp; } while (0) +#define MP_ARRAY_SIZE(s) (sizeof(s) / sizeof((s)[0])) + +// align must be a power of two (align >= 1), x >= 0 +#define MP_ALIGN_UP(x, align) (((x) + (align) - 1) & ~((align) - 1)) +#define MP_ALIGN_DOWN(x, align) ((x) & ~((align) - 1)) + +#define CONTROL_OK 1 +#define CONTROL_TRUE 1 +#define CONTROL_FALSE 0 +#define CONTROL_UNKNOWN -1 +#define CONTROL_ERROR -2 +#define CONTROL_NA -3 + +enum stream_type { + STREAM_VIDEO, + STREAM_AUDIO, + STREAM_SUB, + STREAM_TYPE_COUNT, +}; + +extern const char *mplayer_version; +extern const char *mplayer_builddate; + +char *mp_format_time(double time, bool fractions); +char *mp_format_time_fmt(const char *fmt, double time); + +struct mp_rect { + int x0, y0; + int x1, y1; +}; + +void mp_rect_union(struct mp_rect *rc, const struct mp_rect *src); +bool mp_rect_intersection(struct mp_rect *rc, const struct mp_rect *rc2); + +char *mp_append_utf8_buffer(char *buffer, uint32_t codepoint); + +struct bstr; +bool mp_parse_escape(struct bstr *code, char **str); + +#endif /* MPLAYER_MPCOMMON_H */ diff --git a/common/cpudetect.c b/common/cpudetect.c new file mode 100644 index 0000000000..ea28cf4dd4 --- /dev/null +++ b/common/cpudetect.c @@ -0,0 +1,56 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <string.h> +#include <stdlib.h> + +#include <libavutil/cpu.h> +#include "compat/libav.h" + +#include "config.h" +#include "common/cpudetect.h" +#include "common/msg.h" + +CpuCaps gCpuCaps; + +static void dump_flag(const char *name, bool val) +{ + mp_msg(MSGT_CPUDETECT, MSGL_V, "CPU: %s: %s\n", name, + val ? "enabled" : "disabled"); +} + +void GetCpuCaps(CpuCaps *c) +{ + memset(c, 0, sizeof(*c)); + int flags = av_get_cpu_flags(); +#if ARCH_X86 + c->hasMMX = flags & AV_CPU_FLAG_MMX; + c->hasMMX2 = flags & AV_CPU_FLAG_MMX2; + c->hasSSE = flags & AV_CPU_FLAG_SSE; + c->hasSSE2 = (flags & AV_CPU_FLAG_SSE2) && !(flags & AV_CPU_FLAG_SSE2SLOW); + c->hasSSE3 = (flags & AV_CPU_FLAG_SSE3) && !(flags & AV_CPU_FLAG_SSE3SLOW); + c->hasSSSE3 = flags & AV_CPU_FLAG_SSSE3; +#endif + dump_flag("MMX", c->hasMMX); + dump_flag("MMX2", c->hasMMX2); + dump_flag("SSE", c->hasSSE); + dump_flag("SSE2", c->hasSSE2); + dump_flag("SSE3", c->hasSSE3); + dump_flag("SSSE3", c->hasSSSE3); +} diff --git a/common/cpudetect.h b/common/cpudetect.h new file mode 100644 index 0000000000..d3d9206c65 --- /dev/null +++ b/common/cpudetect.h @@ -0,0 +1,40 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_CPUDETECT_H +#define MPLAYER_CPUDETECT_H + +#include <stdbool.h> +#include "config.h" + +#include "compat/x86_cpu.h" + +typedef struct cpucaps_s { + bool hasMMX; + bool hasMMX2; + bool hasSSE; + bool hasSSE2; + bool hasSSE3; + bool hasSSSE3; +} CpuCaps; + +extern CpuCaps gCpuCaps; + +void GetCpuCaps(CpuCaps *caps); + +#endif /* MPLAYER_CPUDETECT_H */ diff --git a/common/encode.h b/common/encode.h new file mode 100644 index 0000000000..fec14045ed --- /dev/null +++ b/common/encode.h @@ -0,0 +1,21 @@ +#ifndef MPLAYER_ENCODE_H +#define MPLAYER_ENCODE_H + +#include <stdbool.h> + +struct MPOpts; +struct encode_lavc_context; +struct encode_output_conf; + +// interface for mplayer.c +struct encode_lavc_context *encode_lavc_init(struct encode_output_conf *options); +void encode_lavc_finish(struct encode_lavc_context *ctx); +void encode_lavc_free(struct encode_lavc_context *ctx); +void encode_lavc_discontinuity(struct encode_lavc_context *ctx); +bool encode_lavc_showhelp(struct MPOpts *opts); +int encode_lavc_getstatus(struct encode_lavc_context *ctx, char *buf, int bufsize, float relative_position); +void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt); +void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps); +bool encode_lavc_didfail(struct encode_lavc_context *ctx); // check if encoding failed + +#endif diff --git a/common/encode_lavc.c b/common/encode_lavc.c new file mode 100644 index 0000000000..97a45ccbbe --- /dev/null +++ b/common/encode_lavc.c @@ -0,0 +1,1115 @@ +/* + * muxing using libavformat + * Copyright (C) 2010 Nicolas George <george@nsup.org> + * Copyright (C) 2011-2012 Rudolf Polzer <divVerent@xonotic.org> + * + * This file is part of mpv. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <libavutil/avutil.h> + +#include "encode_lavc.h" +#include "common/msg.h" +#include "video/vfcap.h" +#include "options/options.h" +#include "osdep/timer.h" +#include "video/out/vo.h" +#include "talloc.h" +#include "stream/stream.h" + +static int set_to_avdictionary(AVDictionary **dictp, const char *key, + const char *val) +{ + char keybuf[1024]; + char valuebuf[1024]; + + if (key == NULL) { + // we need to split at equals sign + const char *equals = strchr(val, '='); + if (!equals || equals - val >= sizeof(keybuf)) { + mp_msg(MSGT_ENCODE, MSGL_WARN, + "encode-lavc: option '%s' does not contain an equals sign\n", + val); + return 0; + } + memcpy(keybuf, val, equals - val); + keybuf[equals - val] = 0; + key = keybuf; + val = equals + 1; + } + + // hack: support "qscale" key as virtual "global_quality" key that multiplies by QP2LAMBDA + if (!strcmp(key, "qscale")) { + key = "global_quality"; + snprintf(valuebuf, sizeof(valuebuf), + "%.1s(%s)*QP2LAMBDA", + (val[0] == '+' || val[0] == '-') ? val : "", + (val[0] == '+' || val[0] == '-') ? val + 1 : val); + valuebuf[sizeof(valuebuf) - 1] = 0; + val = valuebuf; + } + + mp_msg(MSGT_ENCODE, MSGL_V, + "encode-lavc: setting value '%s' for key '%s'\n", + val, + key); + + if (av_dict_set(dictp, key, *val ? val : NULL, + (val[0] == '+' || val[0] == '-') ? AV_DICT_APPEND : 0) >= 0) + return 1; + + return 0; +} + +static bool value_has_flag(const char *value, const char *flag) +{ + bool state = true; + bool ret = false; + while (*value) { + size_t l = strcspn(value, "+-"); + if (l == 0) { + state = (*value == '+'); + ++value; + } else { + if (l == strlen(flag)) + if (!memcmp(value, flag, l)) + ret = state; + value += l; + } + } + return ret; +} + +#define CHECK_FAIL(ctx, val) \ + if (ctx && (ctx->failed || ctx->finished)) { \ + mp_msg(MSGT_ENCODE, MSGL_ERR, \ + "Called a function on a %s encoding context. Bailing out.\n", \ + ctx->failed ? "failed" : "finished"); \ + return val; \ + } + +int encode_lavc_available(struct encode_lavc_context *ctx) +{ + CHECK_FAIL(ctx, 0); + return ctx && ctx->avc; +} + +int encode_lavc_oformat_flags(struct encode_lavc_context *ctx) +{ + CHECK_FAIL(ctx, 0); + return ctx->avc ? ctx->avc->oformat->flags : 0; +} + +struct encode_lavc_context *encode_lavc_init(struct encode_output_conf *options) +{ + struct encode_lavc_context *ctx; + const char *filename = options->file; + + // STUPID STUPID STUPID STUPID avio + // does not support "-" as file name to mean stdin/stdout + // ffmpeg.c works around this too, the same way + if (!strcmp(filename, "-")) + filename = "pipe:1"; + + if (filename && ( + !strcmp(filename, "/dev/stdout") || + !strcmp(filename, "pipe:") || + !strcmp(filename, "pipe:1"))) + mp_msg_stdout_in_use = 1; + + ctx = talloc_zero(NULL, struct encode_lavc_context); + encode_lavc_discontinuity(ctx); + ctx->options = options; + + ctx->avc = avformat_alloc_context(); + + if (ctx->options->format) { + char *tok; + const char *in = ctx->options->format; + while (*in) { + tok = av_get_token(&in, ","); + ctx->avc->oformat = av_guess_format(tok, filename, NULL); + av_free(tok); + if (ctx->avc->oformat) + break; + if (*in) + ++in; + } + } else + ctx->avc->oformat = av_guess_format(NULL, filename, NULL); + + if (!ctx->avc->oformat) { + encode_lavc_fail(ctx, "encode-lavc: format not found\n"); + return NULL; + } + + av_strlcpy(ctx->avc->filename, filename, + sizeof(ctx->avc->filename)); + + ctx->foptions = NULL; + if (ctx->options->fopts) { + char **p; + for (p = ctx->options->fopts; *p; ++p) { + if (!set_to_avdictionary(&ctx->foptions, NULL, *p)) + mp_msg(MSGT_ENCODE, MSGL_WARN, + "encode-lavc: could not set option %s\n", *p); + } + } + + if (ctx->options->vcodec) { + char *tok; + const char *in = ctx->options->vcodec; + while (*in) { + tok = av_get_token(&in, ","); + ctx->vc = avcodec_find_encoder_by_name(tok); + av_free(tok); + if (ctx->vc && ctx->vc->type != AVMEDIA_TYPE_VIDEO) + ctx->vc = NULL; + if (ctx->vc) + break; + if (*in) + ++in; + } + } else + ctx->vc = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL, + ctx->avc->filename, NULL, + AVMEDIA_TYPE_VIDEO)); + + if (ctx->options->acodec) { + char *tok; + const char *in = ctx->options->acodec; + while (*in) { + tok = av_get_token(&in, ","); + ctx->ac = avcodec_find_encoder_by_name(tok); + av_free(tok); + if (ctx->ac && ctx->ac->type != AVMEDIA_TYPE_AUDIO) + ctx->ac = NULL; + if (ctx->ac) + break; + if (*in) + ++in; + } + } else + ctx->ac = avcodec_find_encoder(av_guess_codec(ctx->avc->oformat, NULL, + ctx->avc->filename, NULL, + AVMEDIA_TYPE_AUDIO)); + + if (!ctx->vc && !ctx->ac) { + encode_lavc_fail( + ctx, "encode-lavc: neither audio nor video codec was found\n"); + return NULL; + } + + /* taken from ffmpeg unchanged + * TODO turn this into an option if anyone needs this */ + + ctx->avc->max_delay = 0.7 * AV_TIME_BASE; + + ctx->abytes = 0; + ctx->vbytes = 0; + ctx->frames = 0; + + if (options->video_first) + ctx->video_first = true; + if (options->audio_first) + ctx->audio_first = true; + + return ctx; +} + +int encode_lavc_start(struct encode_lavc_context *ctx) +{ + AVDictionaryEntry *de; + unsigned i; + + if (ctx->header_written < 0) + return 0; + if (ctx->header_written > 0) + return 1; + + CHECK_FAIL(ctx, 0); + + if (ctx->expect_video) { + for (i = 0; i < ctx->avc->nb_streams; ++i) + if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_VIDEO) + break; + if (i >= ctx->avc->nb_streams) { + encode_lavc_fail(ctx, + "encode-lavc: video stream missing, invalid codec?\n"); + return 0; + } + } + if (ctx->expect_audio) { + for (i = 0; i < ctx->avc->nb_streams; ++i) + if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_AUDIO) + break; + if (i >= ctx->avc->nb_streams) { + encode_lavc_fail(ctx, + "encode-lavc: audio stream missing, invalid codec?\n"); + return 0; + } + } + + ctx->header_written = -1; + + if (!(ctx->avc->oformat->flags & AVFMT_NOFILE)) { + mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening output file: %s\n", + ctx->avc->filename); + + if (avio_open(&ctx->avc->pb, ctx->avc->filename, + AVIO_FLAG_WRITE) < 0) { + encode_lavc_fail(ctx, "encode-lavc: could not open '%s'\n", + ctx->avc->filename); + return 0; + } + } + + ctx->t0 = mp_time_sec(); + + mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening muxer: %s [%s]\n", + ctx->avc->oformat->long_name, ctx->avc->oformat->name); + + if (avformat_write_header(ctx->avc, &ctx->foptions) < 0) { + encode_lavc_fail(ctx, "encode-lavc: could not write header\n"); + return 0; + } + + for (de = NULL; (de = av_dict_get(ctx->foptions, "", de, + AV_DICT_IGNORE_SUFFIX));) + mp_msg(MSGT_ENCODE, MSGL_WARN, "ofopts: key '%s' not found.\n", de->key); + av_dict_free(&ctx->foptions); + + ctx->header_written = 1; + return 1; +} + +void encode_lavc_free(struct encode_lavc_context *ctx) +{ + if (!ctx) + return; + + if (!ctx->finished) + encode_lavc_fail(ctx, + "called encode_lavc_free without encode_lavc_finish\n"); + + talloc_free(ctx); +} + +void encode_lavc_finish(struct encode_lavc_context *ctx) +{ + unsigned i; + + if (!ctx) + return; + + if (ctx->finished) + return; + + if (ctx->avc) { + if (ctx->header_written > 0) + av_write_trailer(ctx->avc); // this is allowed to fail + + for (i = 0; i < ctx->avc->nb_streams; i++) { + switch (ctx->avc->streams[i]->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + if (ctx->twopass_bytebuffer_v) { + char *stats = ctx->avc->streams[i]->codec->stats_out; + if (stats) + stream_write_buffer(ctx->twopass_bytebuffer_v, + stats, strlen(stats)); + } + break; + case AVMEDIA_TYPE_AUDIO: + if (ctx->twopass_bytebuffer_a) { + char *stats = ctx->avc->streams[i]->codec->stats_out; + if (stats) + stream_write_buffer(ctx->twopass_bytebuffer_a, + stats, strlen(stats)); + } + break; + default: + break; + } + avcodec_close(ctx->avc->streams[i]->codec); + talloc_free(ctx->avc->streams[i]->codec->stats_in); + av_free(ctx->avc->streams[i]->codec); + av_free(ctx->avc->streams[i]->info); + av_free(ctx->avc->streams[i]); + } + + if (ctx->twopass_bytebuffer_v) { + free_stream(ctx->twopass_bytebuffer_v); + ctx->twopass_bytebuffer_v = NULL; + } + + if (ctx->twopass_bytebuffer_a) { + free_stream(ctx->twopass_bytebuffer_a); + ctx->twopass_bytebuffer_a = NULL; + } + + mp_msg(MSGT_ENCODE, MSGL_INFO, "vo-lavc: encoded %lld bytes\n", + ctx->vbytes); + mp_msg(MSGT_ENCODE, MSGL_INFO, "ao-lavc: encoded %lld bytes\n", + ctx->abytes); + if (ctx->avc->pb) { + mp_msg(MSGT_ENCODE, MSGL_INFO, + "encode-lavc: muxing overhead %lld bytes\n", + (long long) (avio_size(ctx->avc->pb) - ctx->vbytes + - ctx->abytes)); + avio_close(ctx->avc->pb); + } + + av_free(ctx->avc); + } + + ctx->finished = true; +} + +void encode_lavc_set_video_fps(struct encode_lavc_context *ctx, float fps) +{ + ctx->vo_fps = fps; +} + +static void encode_2pass_prepare(struct encode_lavc_context *ctx, + AVDictionary **dictp, + AVStream *stream, struct stream **bytebuf, + const char *prefix) +{ + if (!*bytebuf) { + char buf[sizeof(ctx->avc->filename) + 12]; + AVDictionaryEntry *de = av_dict_get(ctx->voptions, "flags", NULL, 0); + + snprintf(buf, sizeof(buf), "%s-%s-pass1.log", ctx->avc->filename, + prefix); + buf[sizeof(buf) - 1] = 0; + + if (value_has_flag(de ? de->value : "", "pass2")) { + if (!(*bytebuf = stream_open(buf, NULL))) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not open '%s', " + "disabling 2-pass encoding at pass 2\n", prefix, buf); + stream->codec->flags &= ~CODEC_FLAG_PASS2; + set_to_avdictionary(dictp, "flags", "-pass2"); + } else { + struct bstr content = stream_read_complete(*bytebuf, NULL, + 1000000000); + if (content.start == NULL) { + mp_msg(MSGT_ENCODE, MSGL_WARN, "%s: could not read '%s', " + "disabling 2-pass encoding at pass 1\n", + prefix, ctx->avc->filename); + } else { + content.start[content.len] = 0; + stream->codec->stats_in = content.start; + } + free_stream(*bytebuf); + *bytebuf = NULL; + } + } + + if (value_has_flag(de ? de->value : "", "pass1")) { + if (!(*bytebuf = open_output_stream(buf, NULL))) { + mp_msg( + MSGT_ENCODE, MSGL_WARN, + "%s: could not open '%s', disabling " + "2-pass encoding at pass 1\n", + prefix, ctx->avc->filename); + set_to_avdictionary(dictp, "flags", "-pass1"); + } + } + } +} + +AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx, + enum AVMediaType mt) +{ + AVDictionaryEntry *de; + AVStream *stream = NULL; + char **p; + int i; + + CHECK_FAIL(ctx, NULL); + + if (ctx->header_written) + return NULL; + + for (i = 0; i < ctx->avc->nb_streams; ++i) + if (ctx->avc->streams[i]->codec->codec_type == mt) + // already have a stream of that type, this cannot really happen + return NULL; + + if (ctx->avc->nb_streams == 0) { + // if this stream isn't stream #0, allocate a dummy stream first for + // the next loop to use + if (mt == AVMEDIA_TYPE_VIDEO && ctx->audio_first) { + mp_msg(MSGT_ENCODE, MSGL_INFO, + "vo-lavc: preallocated audio stream for later use\n"); + avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now + } + if (mt == AVMEDIA_TYPE_AUDIO && ctx->video_first) { + mp_msg(MSGT_ENCODE, MSGL_INFO, + "ao-lavc: preallocated video stream for later use\n"); + avformat_new_stream(ctx->avc, NULL); // this one is AVMEDIA_TYPE_UNKNOWN for now + } + } else { + // find possibly preallocated stream + for (i = 0; i < ctx->avc->nb_streams; ++i) + if (ctx->avc->streams[i]->codec->codec_type == AVMEDIA_TYPE_UNKNOWN) // preallocated stream + stream = ctx->avc->streams[i]; + } + if (!stream) + stream = avformat_new_stream(ctx->avc, NULL); + + if (ctx->timebase.den == 0) { + AVRational r; + + if (ctx->options->fps > 0) + r = av_d2q(ctx->options->fps, ctx->options->fps * 1001 + 2); + else if (ctx->options->autofps && ctx->vo_fps > 0) { + r = av_d2q(ctx->vo_fps, ctx->vo_fps * 1001 + 2); + mp_msg( + MSGT_ENCODE, MSGL_INFO, "vo-lavc: option --ofps not specified " + "but --oautofps is active, using guess of %u/%u\n", + (unsigned)r.num, (unsigned)r.den); + } else { + // we want to handle: + // 1/25 + // 1001/24000 + // 1001/30000 + // for this we would need 120000fps... + // however, mpeg-4 only allows 16bit values + // so let's take 1001/30000 out + r.num = 24000; + r.den = 1; + mp_msg( + MSGT_ENCODE, MSGL_INFO, "vo-lavc: option --ofps not specified " + "and fps could not be inferred, using guess of %u/%u\n", + (unsigned)r.num, (unsigned)r.den); + } + + if (ctx->vc && ctx->vc->supported_framerates) + r = ctx->vc->supported_framerates[av_find_nearest_q_idx(r, + ctx->vc->supported_framerates)]; + + ctx->timebase.num = r.den; + ctx->timebase.den = r.num; + } + + switch (mt) { + case AVMEDIA_TYPE_VIDEO: + if (!ctx->vc) { + encode_lavc_fail(ctx, "vo-lavc: encoder not found\n"); + return NULL; + } + avcodec_get_context_defaults3(stream->codec, ctx->vc); + + // stream->time_base = ctx->timebase; + // doing this breaks mpeg2ts in ffmpeg + // which doesn't properly force the time base to be 90000 + // furthermore, ffmpeg.c doesn't do this either and works + + stream->codec->time_base = ctx->timebase; + + ctx->voptions = NULL; + + if (ctx->options->vopts) + for (p = ctx->options->vopts; *p; ++p) + if (!set_to_avdictionary(&ctx->voptions, NULL, *p)) + mp_msg(MSGT_ENCODE, MSGL_WARN, + "vo-lavc: could not set option %s\n", *p); + + de = av_dict_get(ctx->voptions, "global_quality", NULL, 0); + if (de) + set_to_avdictionary(&ctx->voptions, "flags", "+qscale"); + + if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER) + set_to_avdictionary(&ctx->voptions, "flags", "+global_header"); + + encode_2pass_prepare(ctx, &ctx->voptions, stream, + &ctx->twopass_bytebuffer_v, + "vo-lavc"); + break; + + case AVMEDIA_TYPE_AUDIO: + if (!ctx->ac) { + encode_lavc_fail(ctx, "ao-lavc: encoder not found\n"); + return NULL; + } + avcodec_get_context_defaults3(stream->codec, ctx->ac); + + stream->codec->time_base = ctx->timebase; + + ctx->aoptions = NULL; + + if (ctx->options->aopts) + for (p = ctx->options->aopts; *p; ++p) + if (!set_to_avdictionary(&ctx->aoptions, NULL, *p)) + mp_msg(MSGT_ENCODE, MSGL_WARN, + "ao-lavc: could not set option %s\n", *p); + + de = av_dict_get(ctx->aoptions, "global_quality", NULL, 0); + if (de) + set_to_avdictionary(&ctx->aoptions, "flags", "+qscale"); + + if (ctx->avc->oformat->flags & AVFMT_GLOBALHEADER) + set_to_avdictionary(&ctx->aoptions, "flags", "+global_header"); + + encode_2pass_prepare(ctx, &ctx->aoptions, stream, + &ctx->twopass_bytebuffer_a, + "ao-lavc"); + break; + + default: + encode_lavc_fail(ctx, "encode-lavc: requested invalid stream type\n"); + return NULL; + } + + return stream; +} + +AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx, + AVStream *stream) +{ + CHECK_FAIL(ctx, NULL); + + switch (stream->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + return ctx->vc; + case AVMEDIA_TYPE_AUDIO: + return ctx->ac; + default: + break; + } + return NULL; +} + +int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream) +{ + AVDictionaryEntry *de; + int ret; + + CHECK_FAIL(ctx, -1); + + switch (stream->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening video encoder: %s [%s]\n", + ctx->vc->long_name, ctx->vc->name); + + if (ctx->vc->capabilities & CODEC_CAP_EXPERIMENTAL) { + stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; + mp_msg(MSGT_ENCODE, MSGL_WARN, _( + "\n\n" + " ********************************************\n" + " **** Experimental VIDEO codec selected! ****\n" + " ********************************************\n\n" + "This means the output file may be broken or bad.\n" + "Possible reasons, problems, workarounds:\n" + "- Codec implementation in ffmpeg/libav is not finished yet.\n" + " Try updating ffmpeg or libav.\n" + "- Bad picture quality, blocks, blurriness.\n" + " Experiment with codec settings (--ovcopts) to maybe still get the\n" + " desired quality output at the expense of bitrate.\n" + "- Slow compression.\n" + " Bear with it.\n" + "- Crashes.\n" + " Happens. Try varying options to work around.\n" + "If none of this helps you, try another codec in place of %s.\n\n"), + ctx->vc->name); + } + + ret = avcodec_open2(stream->codec, ctx->vc, &ctx->voptions); + + // complain about all remaining options, then free the dict + for (de = NULL; (de = av_dict_get(ctx->voptions, "", de, + AV_DICT_IGNORE_SUFFIX));) + mp_msg(MSGT_ENCODE, MSGL_WARN, "ovcopts: key '%s' not found.\n", + de->key); + av_dict_free(&ctx->voptions); + + break; + case AVMEDIA_TYPE_AUDIO: + mp_msg(MSGT_ENCODE, MSGL_INFO, "Opening audio encoder: %s [%s]\n", + ctx->ac->long_name, ctx->ac->name); + + if (ctx->ac->capabilities & CODEC_CAP_EXPERIMENTAL) { + stream->codec->strict_std_compliance = FF_COMPLIANCE_EXPERIMENTAL; + mp_msg(MSGT_ENCODE, MSGL_WARN, _( + "\n\n" + " ********************************************\n" + " **** Experimental AUDIO codec selected! ****\n" + " ********************************************\n\n" + "This means the output file may be broken or bad.\n" + "Possible reasons, problems, workarounds:\n" + "- Codec implementation in ffmpeg/libav is not finished yet.\n" + " Try updating ffmpeg or libav.\n" + "- Bad sound quality, noise, clicking, whistles, choppiness.\n" + " Experiment with codec settings (--oacopts) to maybe still get the\n" + " desired quality output at the expense of bitrate.\n" + "- Slow compression.\n" + " Bear with it.\n" + "- Crashes.\n" + " Happens. Try varying options to work around.\n" + "If none of this helps you, try another codec in place of %s.\n\n"), + ctx->ac->name); + } + ret = avcodec_open2(stream->codec, ctx->ac, &ctx->aoptions); + + // complain about all remaining options, then free the dict + for (de = NULL; (de = av_dict_get(ctx->aoptions, "", de, + AV_DICT_IGNORE_SUFFIX));) + mp_msg(MSGT_ENCODE, MSGL_WARN, "oacopts: key '%s' not found.\n", + de->key); + av_dict_free(&ctx->aoptions); + + break; + default: + ret = -1; + break; + } + + if (ret < 0) + encode_lavc_fail(ctx, + "unable to open encoder (see above for the cause)\n"); + + return ret; +} + +void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream) +{ + CHECK_FAIL(ctx, ); + + switch (stream->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + if (ctx->twopass_bytebuffer_v) + if (stream->codec->stats_out) + stream_write_buffer(ctx->twopass_bytebuffer_v, + stream->codec->stats_out, + strlen(stream->codec->stats_out)); + break; + case AVMEDIA_TYPE_AUDIO: + if (ctx->twopass_bytebuffer_a) + if (stream->codec->stats_out) + stream_write_buffer(ctx->twopass_bytebuffer_a, + stream->codec->stats_out, + strlen(stream->codec->stats_out)); + break; + default: + break; + } +} + +int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet) +{ + int r; + + CHECK_FAIL(ctx, -1); + + if (ctx->header_written <= 0) + return -1; + + mp_msg( + MSGT_ENCODE, MSGL_DBG2, + "encode-lavc: write frame: stream %d ptsi %d (%f) dtsi %d (%f) size %d\n", + (int)packet->stream_index, + (int)packet->pts, + packet->pts + * (double)ctx->avc->streams[packet->stream_index]->time_base.num + / (double)ctx->avc->streams[packet->stream_index]->time_base.den, + (int)packet->dts, + packet->dts + * (double)ctx->avc->streams[packet->stream_index]->time_base.num + / (double)ctx->avc->streams[packet->stream_index]->time_base.den, + (int)packet->size); + + switch (ctx->avc->streams[packet->stream_index]->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + ctx->vbytes += packet->size; + ++ctx->frames; + break; + case AVMEDIA_TYPE_AUDIO: + ctx->abytes += packet->size; + ctx->audioseconds += packet->duration + * (double)ctx->avc->streams[packet->stream_index]->time_base.num + / (double)ctx->avc->streams[packet->stream_index]->time_base.den; + break; + default: + break; + } + + r = av_interleaved_write_frame(ctx->avc, packet); + + return r; +} + +int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, + enum AVPixelFormat pix_fmt) +{ + CHECK_FAIL(ctx, 0); + + if (!ctx->vc) + return 0; + if (pix_fmt == AV_PIX_FMT_NONE) + return 0; + + if (!ctx->vc->pix_fmts) + return VFCAP_CSP_SUPPORTED; + else { + const enum AVPixelFormat *p; + for (p = ctx->vc->pix_fmts; *p >= 0; ++p) { + if (pix_fmt == *p) + return VFCAP_CSP_SUPPORTED; + } + } + return 0; +} + +void encode_lavc_discontinuity(struct encode_lavc_context *ctx) +{ + if (!ctx) + return; + + CHECK_FAIL(ctx, ); + + ctx->audio_pts_offset = MP_NOPTS_VALUE; + ctx->last_video_in_pts = MP_NOPTS_VALUE; + ctx->discontinuity_pts_offset = MP_NOPTS_VALUE; +} + +static void encode_lavc_printoptions(void *obj, const char *indent, + const char *subindent, const char *unit, + int filter_and, int filter_eq) +{ + const AVOption *opt = NULL; + char optbuf[32]; + while ((opt = av_opt_next(obj, opt))) { + // if flags are 0, it simply hasn't been filled in yet and may be + // potentially useful + if (opt->flags) + if ((opt->flags & filter_and) != filter_eq) + continue; + /* Don't print CONST's on level one. + * Don't print anything but CONST's on level two. + * Only print items from the requested unit. + */ + if (!unit && opt->type == AV_OPT_TYPE_CONST) + continue; + else if (unit && opt->type != AV_OPT_TYPE_CONST) + continue; + else if (unit && opt->type == AV_OPT_TYPE_CONST + && strcmp(unit, opt->unit)) + continue; + else if (unit && opt->type == AV_OPT_TYPE_CONST) + mp_msg(MSGT_ENCODE, MSGL_INFO, "%s", subindent); + else + mp_msg(MSGT_ENCODE, MSGL_INFO, "%s", indent); + + switch (opt->type) { + case AV_OPT_TYPE_FLAGS: + snprintf(optbuf, sizeof(optbuf), "%s=<flags>", opt->name); + break; + case AV_OPT_TYPE_INT: + snprintf(optbuf, sizeof(optbuf), "%s=<int>", opt->name); + break; + case AV_OPT_TYPE_INT64: + snprintf(optbuf, sizeof(optbuf), "%s=<int64>", opt->name); + break; + case AV_OPT_TYPE_DOUBLE: + snprintf(optbuf, sizeof(optbuf), "%s=<double>", opt->name); + break; + case AV_OPT_TYPE_FLOAT: + snprintf(optbuf, sizeof(optbuf), "%s=<float>", opt->name); + break; + case AV_OPT_TYPE_STRING: + snprintf(optbuf, sizeof(optbuf), "%s=<string>", opt->name); + break; + case AV_OPT_TYPE_RATIONAL: + snprintf(optbuf, sizeof(optbuf), "%s=<rational>", opt->name); + break; + case AV_OPT_TYPE_BINARY: + snprintf(optbuf, sizeof(optbuf), "%s=<binary>", opt->name); + break; + case AV_OPT_TYPE_CONST: + snprintf(optbuf, sizeof(optbuf), " [+-]%s", opt->name); + break; + default: + snprintf(optbuf, sizeof(optbuf), "%s", opt->name); + break; + } + optbuf[sizeof(optbuf) - 1] = 0; + mp_msg(MSGT_ENCODE, MSGL_INFO, "%-32s ", optbuf); + if (opt->help) + mp_msg(MSGT_ENCODE, MSGL_INFO, " %s", opt->help); + mp_msg(MSGT_ENCODE, MSGL_INFO, "\n"); + if (opt->unit && opt->type != AV_OPT_TYPE_CONST) + encode_lavc_printoptions(obj, indent, subindent, opt->unit, + filter_and, filter_eq); + } +} + +bool encode_lavc_showhelp(struct MPOpts *opts) +{ + bool help_output = false; + if (av_codec_next(NULL) == NULL) + mp_msg(MSGT_ENCODE, MSGL_ERR, "NO CODECS\n"); +#define CHECKS(str) ((str) && \ + strcmp((str), "help") == 0 ? (help_output |= 1) : 0) +#define CHECKV(strv) ((strv) && (strv)[0] && \ + strcmp((strv)[0], "help") == 0 ? (help_output |= 1) : 0) + if (CHECKS(opts->encode_output.format)) { + AVOutputFormat *c = NULL; + mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output formats:\n"); + while ((c = av_oformat_next(c))) + mp_msg(MSGT_ENCODE, MSGL_INFO, " --of=%-13s %s\n", c->name, + c->long_name ? c->long_name : ""); + av_free(c); + } + if (CHECKV(opts->encode_output.fopts)) { + AVFormatContext *c = avformat_alloc_context(); + AVOutputFormat *format = NULL; + mp_msg(MSGT_ENCODE, MSGL_INFO, + "Available output format ctx->options:\n"); + encode_lavc_printoptions(c, " --ofopts=", " ", NULL, + AV_OPT_FLAG_ENCODING_PARAM, + AV_OPT_FLAG_ENCODING_PARAM); + av_free(c); + while ((format = av_oformat_next(format))) { + if (format->priv_class) { + mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for --of=%s:\n", + format->name); + encode_lavc_printoptions(&format->priv_class, " --ofopts=", + " ", NULL, + AV_OPT_FLAG_ENCODING_PARAM, + AV_OPT_FLAG_ENCODING_PARAM); + } + } + } + if (CHECKV(opts->encode_output.vopts)) { + AVCodecContext *c = avcodec_alloc_context3(NULL); + AVCodec *codec = NULL; + mp_msg(MSGT_ENCODE, MSGL_INFO, + "Available output video codec ctx->options:\n"); + encode_lavc_printoptions( + c, " --ovcopts=", " ", NULL, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_VIDEO_PARAM, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_VIDEO_PARAM); + av_free(c); + while ((codec = av_codec_next(codec))) { + if (!av_codec_is_encoder(codec)) + continue; + if (codec->type != AVMEDIA_TYPE_VIDEO) + continue; + if (opts->encode_output.vcodec && opts->encode_output.vcodec[0] && + strcmp(opts->encode_output.vcodec, codec->name) != 0) + continue; + if (codec->priv_class) { + mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for --ovc=%s:\n", + codec->name); + encode_lavc_printoptions( + &codec->priv_class, " --ovcopts=", + " ", NULL, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_VIDEO_PARAM, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_VIDEO_PARAM); + } + } + } + if (CHECKV(opts->encode_output.aopts)) { + AVCodecContext *c = avcodec_alloc_context3(NULL); + AVCodec *codec = NULL; + mp_msg(MSGT_ENCODE, MSGL_INFO, + "Available output audio codec ctx->options:\n"); + encode_lavc_printoptions( + c, " --oacopts=", " ", NULL, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_AUDIO_PARAM, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_AUDIO_PARAM); + av_free(c); + while ((codec = av_codec_next(codec))) { + if (!av_codec_is_encoder(codec)) + continue; + if (codec->type != AVMEDIA_TYPE_AUDIO) + continue; + if (opts->encode_output.acodec && opts->encode_output.acodec[0] && + strcmp(opts->encode_output.acodec, codec->name) != 0) + continue; + if (codec->priv_class) { + mp_msg(MSGT_ENCODE, MSGL_INFO, "Additionally, for --oac=%s:\n", + codec->name); + encode_lavc_printoptions( + &codec->priv_class, " --oacopts=", + " ", NULL, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_AUDIO_PARAM, + AV_OPT_FLAG_ENCODING_PARAM | + AV_OPT_FLAG_AUDIO_PARAM); + } + } + } + if (CHECKS(opts->encode_output.vcodec)) { + AVCodec *c = NULL; + mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output video codecs:\n"); + while ((c = av_codec_next(c))) { + if (!av_codec_is_encoder(c)) + continue; + if (c->type != AVMEDIA_TYPE_VIDEO) + continue; + mp_msg(MSGT_ENCODE, MSGL_INFO, " --ovc=%-12s %s\n", c->name, + c->long_name ? c->long_name : ""); + } + av_free(c); + } + if (CHECKS(opts->encode_output.acodec)) { + AVCodec *c = NULL; + mp_msg(MSGT_ENCODE, MSGL_INFO, "Available output audio codecs:\n"); + while ((c = av_codec_next(c))) { + if (!av_codec_is_encoder(c)) + continue; + if (c->type != AVMEDIA_TYPE_AUDIO) + continue; + mp_msg(MSGT_ENCODE, MSGL_INFO, " --oac=%-12s %s\n", c->name, + c->long_name ? c->long_name : ""); + } + av_free(c); + } + return help_output; +} + +double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream) +{ + CHECK_FAIL(ctx, 0); + + switch (stream->codec->codec_type) { + case AVMEDIA_TYPE_VIDEO: + return ctx->options->voffset; + case AVMEDIA_TYPE_AUDIO: + return ctx->options->aoffset; + default: + break; + } + return 0; +} + +int encode_lavc_getstatus(struct encode_lavc_context *ctx, + char *buf, int bufsize, + float relative_position) +{ + double now = mp_time_sec(); + float minutes, megabytes, fps, x; + float f = FFMAX(0.0001, relative_position); + if (!ctx) + return -1; + + CHECK_FAIL(ctx, -1); + + minutes = (now - ctx->t0) / 60.0 * (1 - f) / f; + megabytes = ctx->avc->pb ? (avio_size(ctx->avc->pb) / 1048576.0 / f) : 0; + fps = ctx->frames / (now - ctx->t0); + x = ctx->audioseconds / (now - ctx->t0); + if (ctx->frames) + snprintf(buf, bufsize, "{%.1fmin %.1ffps %.1fMB}", + minutes, fps, megabytes); + else if (ctx->audioseconds) + snprintf(buf, bufsize, "{%.1fmin %.2fx %.1fMB}", + minutes, x, megabytes); + else + snprintf(buf, bufsize, "{%.1fmin %.1fMB}", + minutes, megabytes); + buf[bufsize - 1] = 0; + return 0; +} + +void encode_lavc_expect_stream(struct encode_lavc_context *ctx, int mt) +{ + CHECK_FAIL(ctx, ); + + switch (mt) { + case AVMEDIA_TYPE_VIDEO: + ctx->expect_video = true; + break; + case AVMEDIA_TYPE_AUDIO: + ctx->expect_audio = true; + break; + } +} + +bool encode_lavc_didfail(struct encode_lavc_context *ctx) +{ + return ctx && ctx->failed; +} + +void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...) +{ + va_list va; + va_start(va, format); + mp_msg_va(MSGT_ENCODE, MSGL_ERR, format, va); + if (ctx->failed) + return; + ctx->failed = true; + encode_lavc_finish(ctx); +} + +bool encode_lavc_set_csp(struct encode_lavc_context *ctx, + AVStream *stream, enum mp_csp csp) +{ + CHECK_FAIL(ctx, NULL); + + if (ctx->header_written) { + if (stream->codec->colorspace != mp_csp_to_avcol_spc(csp)) + mp_msg(MSGT_ENCODE, MSGL_WARN, + "encode-lavc: can not change color space during encoding\n"); + return false; + } + + stream->codec->colorspace = mp_csp_to_avcol_spc(csp); + return true; +} + +bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx, + AVStream *stream, enum mp_csp_levels lev) +{ + CHECK_FAIL(ctx, NULL); + + if (ctx->header_written) { + if (stream->codec->color_range != mp_csp_levels_to_avcol_range(lev)) + mp_msg(MSGT_ENCODE, MSGL_WARN, + "encode-lavc: can not change color space during encoding\n"); + return false; + } + + stream->codec->color_range = mp_csp_levels_to_avcol_range(lev); + return true; +} + +enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx, + AVStream *stream) +{ + CHECK_FAIL(ctx, 0); + + return avcol_spc_to_mp_csp(stream->codec->colorspace); +} + +enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx, + AVStream *stream) +{ + CHECK_FAIL(ctx, 0); + + return avcol_range_to_mp_csp_levels(stream->codec->color_range); +} + +// vim: ts=4 sw=4 et diff --git a/common/encode_lavc.h b/common/encode_lavc.h new file mode 100644 index 0000000000..15e0a5c7f2 --- /dev/null +++ b/common/encode_lavc.h @@ -0,0 +1,101 @@ +/* + * muxing using libavformat + * Copyright (C) 2011 Rudolf Polzer <divVerent@xonotic.org> + * + * This file is part of mpv. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_ENCODE_LAVC_H +#define MPLAYER_ENCODE_LAVC_H + +#include <libavcodec/avcodec.h> +#include <libavformat/avformat.h> +#include <libavutil/avstring.h> +#include <libavutil/pixfmt.h> +#include <libavutil/opt.h> +#include <libavutil/mathematics.h> + +#include "encode.h" +#include "video/csputils.h" + +struct encode_lavc_context { + struct encode_output_conf *options; + + float vo_fps; + + // these are processed from the options + AVFormatContext *avc; + AVRational timebase; + AVCodec *vc; + AVCodec *ac; + AVDictionary *foptions; + AVDictionary *aoptions; + AVDictionary *voptions; + + // values created during encoding + int header_written; // -1 means currently writing + + // sync to audio mode + double audio_pts_offset; + double last_video_in_pts; + + // anti discontinuity mode + double next_in_pts; + double discontinuity_pts_offset; + + long long abytes; + long long vbytes; + struct stream *twopass_bytebuffer_a; + struct stream *twopass_bytebuffer_v; + double t0; + unsigned int frames; + double audioseconds; + + bool expect_video; + bool expect_audio; + bool video_first; + bool audio_first; + + // has encoding failed? + bool failed; + bool finished; +}; + +// interface for vo/ao drivers +AVStream *encode_lavc_alloc_stream(struct encode_lavc_context *ctx, enum AVMediaType mt); +void encode_lavc_write_stats(struct encode_lavc_context *ctx, AVStream *stream); +int encode_lavc_write_frame(struct encode_lavc_context *ctx, AVPacket *packet); +int encode_lavc_supports_pixfmt(struct encode_lavc_context *ctx, enum AVPixelFormat format); +AVCodec *encode_lavc_get_codec(struct encode_lavc_context *ctx, AVStream *stream); +int encode_lavc_open_codec(struct encode_lavc_context *ctx, AVStream *stream); +int encode_lavc_available(struct encode_lavc_context *ctx); +int encode_lavc_timesyncfailed(struct encode_lavc_context *ctx); +int encode_lavc_start(struct encode_lavc_context *ctx); // returns 1 on success +int encode_lavc_oformat_flags(struct encode_lavc_context *ctx); +double encode_lavc_getoffset(struct encode_lavc_context *ctx, AVStream *stream); +void encode_lavc_fail(struct encode_lavc_context *ctx, const char *format, ...); // report failure of encoding + +bool encode_lavc_set_csp(struct encode_lavc_context *ctx, + AVStream *stream, enum mp_csp csp); +bool encode_lavc_set_csp_levels(struct encode_lavc_context *ctx, + AVStream *stream, enum mp_csp_levels lev); +enum mp_csp encode_lavc_get_csp(struct encode_lavc_context *ctx, + AVStream *stream); +enum mp_csp_levels encode_lavc_get_csp_levels(struct encode_lavc_context *ctx, + AVStream *stream); + +#endif diff --git a/common/global.h b/common/global.h new file mode 100644 index 0000000000..546c585294 --- /dev/null +++ b/common/global.h @@ -0,0 +1,12 @@ +#ifndef MPV_MPV_H +#define MPV_MPV_H + +// This should be accessed by glue code only, never normal code. +// The only purpose of this is to make mpv library-safe. +// Think hard before adding new members. +struct mpv_global { + struct MPOpts *opts; + struct mp_log *log; +}; + +#endif diff --git a/common/msg.c b/common/msg.c new file mode 100644 index 0000000000..dd03be0f73 --- /dev/null +++ b/common/msg.c @@ -0,0 +1,389 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <assert.h> + +#include "talloc.h" + +#include "config.h" +#include "common/global.h" +#include "osdep/getch2.h" +#include "osdep/io.h" + +#ifndef __MINGW32__ +#include <signal.h> +#endif + +#include "common/msg.h" + +bool mp_msg_stdout_in_use = 0; + +struct mp_log_root { + /* This should, at some point, contain all mp_msg related state, instead + * of having global variables (at least as long as we don't want to + * control the terminal, which is global anyway). But for now, there is + * not much. */ + struct mpv_global *global; +}; + +struct mp_log { + struct mp_log_root *root; + const char *prefix; + const char *verbose_prefix; + int legacy_mod; +}; + +// should not exist +static bool initialized; +static struct mp_log *legacy_logs[MSGT_MAX]; + +/* maximum message length of mp_msg */ +#define MSGSIZE_MAX 6144 + +#ifdef _WIN32 +#define WIN32_LEAN_AND_MEAN +#include <windows.h> +#include <io.h> +#define hSTDOUT GetStdHandle(STD_OUTPUT_HANDLE) +#define hSTDERR GetStdHandle(STD_ERROR_HANDLE) +static short stdoutAttrs = 0; +static const unsigned char ansi2win32[10] = { + 0, + FOREGROUND_RED, + FOREGROUND_GREEN, + FOREGROUND_GREEN | FOREGROUND_RED, + FOREGROUND_BLUE, + FOREGROUND_BLUE | FOREGROUND_RED, + FOREGROUND_BLUE | FOREGROUND_GREEN, + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED, + FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_RED +}; +#endif + +int mp_msg_levels[MSGT_MAX]; // verbose level of this module. initialized to -2 +int mp_msg_level_all = MSGL_STATUS; +int verbose = 0; +int mp_msg_color = 1; +int mp_msg_module = 0; +int mp_msg_cancolor = 0; + +static int mp_msg_docolor(void) { + return mp_msg_cancolor && mp_msg_color; +} + +static void mp_msg_do_init(void){ +#ifdef _WIN32 + CONSOLE_SCREEN_BUFFER_INFO cinfo; + DWORD cmode = 0; + GetConsoleMode(hSTDOUT, &cmode); + cmode |= (ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT); + SetConsoleMode(hSTDOUT, cmode); + SetConsoleMode(hSTDERR, cmode); + GetConsoleScreenBufferInfo(hSTDOUT, &cinfo); + stdoutAttrs = cinfo.wAttributes; +#endif +#ifndef __MINGW32__ + struct sigaction sa; + sa.sa_handler = SIG_IGN; + sa.sa_flags = 0; + sigemptyset(&sa.sa_mask); + sigaction(SIGTTOU, &sa, NULL); // just write to stdout if you have to +#endif + int i; + char *env = getenv("MPV_VERBOSE"); + if (env) + verbose = atoi(env); + for(i=0;i<MSGT_MAX;i++) mp_msg_levels[i] = -2; + mp_msg_cancolor = isatty(fileno(stdout)); + mp_msg_levels[MSGT_IDENTIFY] = -1; // no -identify output by default +} + +int mp_msg_test(int mod, int lev) +{ +#ifndef __MINGW32__ + if (lev == MSGL_STATUS) { + // skip status line output if stderr is a tty but in background + if (isatty(2) && tcgetpgrp(2) != getpgrp()) + return false; + } +#endif + return lev <= (mp_msg_levels[mod] == -2 ? mp_msg_level_all + verbose : mp_msg_levels[mod]); +} + +bool mp_msg_test_log(struct mp_log *log, int lev) +{ + return mp_msg_test(log->legacy_mod, lev); +} + +static void set_msg_color(FILE* stream, int lev) +{ + static const int v_colors[10] = {9, 1, 3, 3, -1, -1, 2, 8, 8, 8}; + int c = v_colors[lev]; +#ifdef MP_ANNOY_ME + /* that's only a silly color test */ + { + int c; + static int flag = 1; + if (flag) + for(c = 0; c < 24; c++) + printf("\033[%d;3%dm*** COLOR TEST %d ***\n", c>7, c&7, c); + flag = 0; + } +#endif + if (mp_msg_docolor()) + { +#if defined(_WIN32) && !defined(__CYGWIN__) + HANDLE *wstream = stream == stderr ? hSTDERR : hSTDOUT; + if (c == -1) + c = 7; + SetConsoleTextAttribute(wstream, ansi2win32[c] | FOREGROUND_INTENSITY); +#else + if (c == -1) { + fprintf(stream, "\033[0m"); + } else { + fprintf(stream, "\033[%d;3%dm", c >> 3, c & 7); + } +#endif + } +} + +static void print_msg_module(FILE* stream, struct mp_log *log) +{ + int mod = log->legacy_mod; + int c2 = (mod + 1) % 15 + 1; + +#ifdef _WIN32 + HANDLE *wstream = stream == stderr ? hSTDERR : hSTDOUT; + if (mp_msg_docolor()) + SetConsoleTextAttribute(wstream, ansi2win32[c2&7] | FOREGROUND_INTENSITY); + fprintf(stream, "%9s", log->verbose_prefix); + if (mp_msg_docolor()) + SetConsoleTextAttribute(wstream, stdoutAttrs); +#else + if (mp_msg_docolor()) + fprintf(stream, "\033[%d;3%dm", c2 >> 3, c2 & 7); + fprintf(stream, "%9s", log->verbose_prefix); + if (mp_msg_docolor()) + fprintf(stream, "\033[0;37m"); +#endif + fprintf(stream, ": "); +} + +static void mp_msg_log_va(struct mp_log *log, int lev, const char *format, + va_list va) +{ + char tmp[MSGSIZE_MAX]; + FILE *stream = + (mp_msg_stdout_in_use || (lev == MSGL_STATUS)) ? stderr : stdout; + static int header = 1; + // indicates if last line printed was a status line + static int statusline; + + if (!mp_msg_test_log(log, lev)) return; // do not display + vsnprintf(tmp, MSGSIZE_MAX, format, va); + tmp[MSGSIZE_MAX-2] = '\n'; + tmp[MSGSIZE_MAX-1] = 0; + + /* A status line is normally intended to be overwritten by the next + * status line, and does not end with a '\n'. If we're printing a normal + * line instead after the status one print '\n' to change line. */ + if (statusline && lev != MSGL_STATUS) + fprintf(stderr, "\n"); + statusline = lev == MSGL_STATUS; + + set_msg_color(stream, lev); + if (header) { + if (mp_msg_module) { + print_msg_module(stream, log); + set_msg_color(stream, lev); + } else if (lev >= MSGL_V || verbose) { + fprintf(stream, "[%s] ", log->verbose_prefix); + } else if (log->prefix) { + fprintf(stream, "[%s] ", log->prefix); + } + } + + size_t len = strlen(tmp); + header = len && (tmp[len-1] == '\n' || tmp[len-1] == '\r'); + + fprintf(stream, "%s", tmp); + + if (mp_msg_docolor()) + { +#ifdef _WIN32 + HANDLE *wstream = lev <= MSGL_WARN ? hSTDERR : hSTDOUT; + SetConsoleTextAttribute(wstream, stdoutAttrs); +#else + fprintf(stream, "\033[0m"); +#endif + } + fflush(stream); +} + +void mp_msg_va(int mod, int lev, const char *format, va_list va) +{ + assert(initialized); + assert(mod >= 0 && mod < MSGT_MAX); + mp_msg_log_va(legacy_logs[mod], lev, format, va); +} + +void mp_msg(int mod, int lev, const char *format, ...) +{ + va_list va; + va_start(va, format); + mp_msg_va(mod, lev, format, va); + va_end(va); +} + +// legacy names +static const char *module_text[MSGT_MAX] = { + "global", + "cplayer", + "gplayer", + "vo", + "ao", + "demuxer", + "ds", + "demux", + "header", + "avsync", + "autoq", + "cfgparser", + "decaudio", + "decvideo", + "seek", + "win32", + "open", + "dvd", + "parsees", + "lirc", + "stream", + "cache", + "mencoder", + "xacodec", + "tv", + "osdep", + "spudec", + "playtree", + "input", + "vf", + "osd", + "network", + "cpudetect", + "codeccfg", + "sws", + "vobsub", + "subreader", + "af", + "netst", + "muxer", + "osdmenu", + "identify", + "radio", + "ass", + "loader", + "statusline", + "teletext", +}; + +// Create a new log context, which uses talloc_ctx as talloc parent, and parent +// as logical parent. +// The name is the prefix put before the output. It's usually prefixed by the +// parent's name. If the name starts with "/", the parent's name is not +// prefixed (except in verbose mode), and if it starts with "!", the name is +// not printed at all (except in verbose mode). +struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, + const char *name) +{ + assert(parent); + assert(name); + struct mp_log *log = talloc_zero(talloc_ctx, struct mp_log); + log->root = parent->root; + if (name[0] == '!') { + name = &name[1]; + } else if (name[0] == '/') { + name = &name[1]; + log->prefix = talloc_strdup(log, name); + } else { + log->prefix = parent->prefix + ? talloc_asprintf(log, "%s/%s", parent->prefix, name) + : talloc_strdup(log, name); + } + log->verbose_prefix = parent->prefix + ? talloc_asprintf(log, "%s/%s", parent->prefix, name) + : talloc_strdup(log, name); + if (log->prefix && !log->prefix[0]) + log->prefix = NULL; + if (!log->verbose_prefix[0]) + log->verbose_prefix = "global"; + log->legacy_mod = parent->legacy_mod; + for (int n = 0; n < MSGT_MAX; n++) { + if (module_text[n] && strcmp(name, module_text[n]) == 0) { + log->legacy_mod = n; + break; + } + } + return log; +} + +void mp_msg_init(struct mpv_global *global) +{ + assert(!initialized); + assert(!global->log); + + struct mp_log_root *root = talloc_zero(NULL, struct mp_log_root); + root->global = global; + + struct mp_log dummy = { .root = root }; + struct mp_log *log = mp_log_new(root, &dummy, ""); + for (int n = 0; n < MSGT_MAX; n++) { + char name[80]; + snprintf(name, sizeof(name), "!%s", module_text[n]); + legacy_logs[n] = mp_log_new(root, log, name); + } + mp_msg_do_init(); + + global->log = log; + initialized = true; +} + +struct mpv_global *mp_log_get_global(struct mp_log *log) +{ + return log->root->global; +} + +void mp_msg_uninit(struct mpv_global *global) +{ + talloc_free(global->log->root); + global->log = NULL; + initialized = false; +} + +void mp_msg_log(struct mp_log *log, int lev, const char *format, ...) +{ + va_list va; + va_start(va, format); + mp_msg_log_va(log, lev, format, va); + va_end(va); +} diff --git a/common/msg.h b/common/msg.h new file mode 100644 index 0000000000..889d99ae62 --- /dev/null +++ b/common/msg.h @@ -0,0 +1,180 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_MP_MSG_H +#define MPLAYER_MP_MSG_H + +#include <stdarg.h> +#include <stdbool.h> + +struct mp_log; + +// defined in mplayer.c +extern int verbose; + +/* No-op macro to mark translated strings in the sources */ +#define _(x) x + +// verbosity elevel: + +/* Only messages level MSGL_FATAL-MSGL_STATUS should be translated, + * messages level MSGL_V and above should not be translated. */ + +#define MSGL_FATAL 0 // will exit/abort +#define MSGL_ERR 1 // continues +#define MSGL_WARN 2 // only warning +#define MSGL_HINT 3 // short help message +#define MSGL_INFO 4 // -quiet +#define MSGL_STATUS 5 // v=0 +#define MSGL_V 6 // v=1 +#define MSGL_DBG2 7 // v=2 +#define MSGL_DBG3 8 // v=3 +#define MSGL_DBG4 9 // v=4 +#define MSGL_DBG5 10 // v=5 + +#define MSGL_FIXME 1 // for conversions from printf where the appropriate MSGL is not known; set equal to ERR for obtrusiveness +#define MSGT_FIXME 0 // for conversions from printf where the appropriate MSGT is not known; set equal to GLOBAL for obtrusiveness + +// code/module: + +#define MSGT_GLOBAL 0 // common player stuff errors +#define MSGT_CPLAYER 1 // console player (mplayer.c) + +#define MSGT_VO 3 // libvo +#define MSGT_AO 4 // libao + +#define MSGT_DEMUXER 5 // demuxer.c (general stuff) +#define MSGT_DS 6 // demux stream (add/read packet etc) +#define MSGT_DEMUX 7 // fileformat-specific stuff (demux_*.c) +#define MSGT_HEADER 8 // fileformat-specific header (*header.c) + +#define MSGT_AVSYNC 9 // mplayer.c timer stuff +#define MSGT_AUTOQ 10 // mplayer.c auto-quality stuff + +#define MSGT_CFGPARSER 11 // cfgparser.c + +#define MSGT_DECAUDIO 12 // av decoder +#define MSGT_DECVIDEO 13 + +#define MSGT_SEEK 14 // seeking code +#define MSGT_WIN32 15 // win32 dll stuff +#define MSGT_OPEN 16 // open.c (stream opening) +#define MSGT_DVD 17 // open.c (DVD init/read/seek) + +#define MSGT_PARSEES 18 // parse_es.c (mpeg stream parser) +#define MSGT_LIRC 19 // lirc_mp.c and input lirc driver + +#define MSGT_STREAM 20 // stream.c +#define MSGT_CACHE 21 // cache2.c + +#define MSGT_ENCODE 22 // now encode_lavc.c + +#define MSGT_XACODEC 23 // XAnim codecs + +#define MSGT_TV 24 // TV input subsystem + +#define MSGT_OSDEP 25 // OS-dependent parts + +#define MSGT_SPUDEC 26 // spudec.c + +#define MSGT_PLAYTREE 27 // Playtree handeling (playtree.c, playtreeparser.c) + +#define MSGT_INPUT 28 + +#define MSGT_VFILTER 29 + +#define MSGT_OSD 30 + +#define MSGT_NETWORK 31 + +#define MSGT_CPUDETECT 32 + +#define MSGT_CODECCFG 33 + +#define MSGT_SWS 34 + +#define MSGT_VOBSUB 35 +#define MSGT_SUBREADER 36 + +#define MSGT_AFILTER 37 // Audio filter messages + +#define MSGT_NETST 38 // Netstream + +#define MSGT_MUXER 39 // muxer layer + +#define MSGT_IDENTIFY 41 // -identify output + +#define MSGT_RADIO 42 + +#define MSGT_ASS 43 // libass messages + +#define MSGT_LOADER 44 // dll loader messages + +#define MSGT_STATUSLINE 45 // playback/encoding status line + +#define MSGT_TELETEXT 46 // Teletext decoder + +#define MSGT_MAX 47 + +int mp_msg_test(int mod, int lev); +bool mp_msg_test_log(struct mp_log *log, int lev); + +#include "config.h" +#include "common/common.h" + +// Note: using mp_msg_log or the MP_ERR/... macros is preferred. +void mp_msg_va(int mod, int lev, const char *format, va_list va); +void mp_msg(int mod, int lev, const char *format, ... ) PRINTF_ATTRIBUTE(3, 4); + +struct mp_log *mp_log_new(void *talloc_ctx, struct mp_log *parent, + const char *name); + +void mp_msg_log(struct mp_log *log, int lev, const char *format, ...) + PRINTF_ATTRIBUTE(3, 4); + +// Convenience macros, typically called with a pointer to a context struct +// as first argument, which has a "struct mp_log log;" member. + +#define MP_MSG(obj, lev, ...) mp_msg_log((obj)->log, lev, __VA_ARGS__) +#define MP_MSGT(obj, lev, ...) mp_msgt_log((obj)->log, lev, __VA_ARGS__) + +#define MP_FATAL(obj, ...) MP_MSG(obj, MSGL_FATAL, __VA_ARGS__) +#define MP_ERR(obj, ...) MP_MSG(obj, MSGL_ERR, __VA_ARGS__) +#define MP_WARN(obj, ...) MP_MSG(obj, MSGL_WARN, __VA_ARGS__) +#define MP_INFO(obj, ...) MP_MSG(obj, MSGL_INFO, __VA_ARGS__) +#define MP_VERBOSE(obj, ...) MP_MSG(obj, MSGL_V, __VA_ARGS__) +#define MP_DBG(obj, ...) MP_MSG(obj, MSGL_DBG2, __VA_ARGS__) +#define MP_TRACE(obj, ...) MP_MSG(obj, MSGL_DBG5, __VA_ARGS__) + +#define mp_fatal(log, ...) mp_msg_log(log, MSGL_FATAL, __VA_ARGS__) +#define mp_err(log, ...) mp_msg_log(log, MSGL_ERR, __VA_ARGS__) +#define mp_warn(log, ...) mp_msg_log(log, MSGL_WARN, __VA_ARGS__) +#define mp_info(log, ...) mp_msg_log(log, MSGL_INFO, __VA_ARGS__) +#define mp_verbose(log, ...) mp_msg_log(log, MSGL_V, __VA_ARGS__) +#define mp_dbg(log, ...) mp_msg_log(log, MSGL_DBG2, __VA_ARGS__) +#define mp_trace(log, ...) mp_msg_log(log, MSGL_DBG5, __VA_ARGS__) + +struct mpv_global; +void mp_msg_init(struct mpv_global *global); +void mp_msg_uninit(struct mpv_global *global); + +struct mpv_global *mp_log_get_global(struct mp_log *log); + +extern bool mp_msg_stdout_in_use; + +#endif /* MPLAYER_MP_MSG_H */ diff --git a/common/playlist.c b/common/playlist.c new file mode 100644 index 0000000000..297cb4d379 --- /dev/null +++ b/common/playlist.c @@ -0,0 +1,241 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include "config.h" +#include "playlist.h" +#include "common/common.h" +#include "talloc.h" +#include "options/path.h" + +struct playlist_entry *playlist_entry_new(const char *filename) +{ + struct playlist_entry *e = talloc_zero(NULL, struct playlist_entry); + e->filename = talloc_strdup(e, filename); + return e; +} + +void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value) +{ + struct playlist_param p = {bstrdup(e, name), bstrdup(e, value)}; + MP_TARRAY_APPEND(e, e->params, e->num_params, p); +} + +void playlist_entry_add_params(struct playlist_entry *e, + struct playlist_param *params, + int num_params) +{ + for (int n = 0; n < num_params; n++) + playlist_entry_add_param(e, params[n].name, params[n].value); +} + +// Add entry "add" after entry "after". +// If "after" is NULL, add as first entry. +// Post condition: add->prev == after +void playlist_insert(struct playlist *pl, struct playlist_entry *after, + struct playlist_entry *add) +{ + assert(pl && add->pl == NULL && add->next == NULL && add->prev == NULL); + if (after) { + assert(after->pl == pl); + assert(pl->first && pl->last); + } + add->prev = after; + if (after) { + add->next = after->next; + after->next = add; + } else { + add->next = pl->first; + pl->first = add; + } + if (add->next) { + add->next->prev = add; + } else { + pl->last = add; + } + add->pl = pl; + talloc_steal(pl, add); +} + +void playlist_add(struct playlist *pl, struct playlist_entry *add) +{ + playlist_insert(pl, pl->last, add); +} + +static void playlist_unlink(struct playlist *pl, struct playlist_entry *entry) +{ + assert(pl && entry->pl == pl); + + if (pl->current == entry) { + pl->current = entry->next; + pl->current_was_replaced = true; + } + + if (entry->next) { + entry->next->prev = entry->prev; + } else { + pl->last = entry->prev; + } + if (entry->prev) { + entry->prev->next = entry->next; + } else { + pl->first = entry->next; + } + entry->next = entry->prev = NULL; + // xxx: we'd want to reset the talloc parent of entry + entry->pl = NULL; +} + +void playlist_remove(struct playlist *pl, struct playlist_entry *entry) +{ + playlist_unlink(pl, entry); + talloc_free(entry); +} + +void playlist_clear(struct playlist *pl) +{ + while (pl->first) + playlist_remove(pl, pl->first); + assert(!pl->current); + pl->current_was_replaced = false; +} + +// Moves entry such that entry->prev = at (even if at is NULL) +void playlist_move(struct playlist *pl, struct playlist_entry *entry, + struct playlist_entry *at) +{ + struct playlist_entry *save_current = pl->current; + bool save_replaced = pl->current_was_replaced; + + playlist_unlink(pl, entry); + playlist_insert(pl, at ? at->prev : pl->last, entry); + + pl->current = save_current; + pl->current_was_replaced = save_replaced; +} + +void playlist_add_file(struct playlist *pl, const char *filename) +{ + playlist_add(pl, playlist_entry_new(filename)); +} + +static int playlist_count(struct playlist *pl) +{ + int c = 0; + for (struct playlist_entry *e = pl->first; e; e = e->next) + c++; + return c; +} + +void playlist_shuffle(struct playlist *pl) +{ + struct playlist_entry *save_current = pl->current; + bool save_replaced = pl->current_was_replaced; + int count = playlist_count(pl); + struct playlist_entry **arr = talloc_array(NULL, struct playlist_entry *, + count); + for (int n = 0; n < count; n++) { + arr[n] = pl->first; + playlist_unlink(pl, pl->first); + } + for (int n = 0; n < count; n++) { + int other = (int)((double)(count) * rand() / (RAND_MAX + 1.0)); + struct playlist_entry *tmp = arr[n]; + arr[n] = arr[other]; + arr[other] = tmp; + } + for (int n = 0; n < count; n++) + playlist_add(pl, arr[n]); + talloc_free(arr); + pl->current = save_current; + pl->current_was_replaced = save_replaced; +} + +struct playlist_entry *playlist_get_next(struct playlist *pl, int direction) +{ + assert(direction == -1 || direction == +1); + if (!pl->current) + return NULL; + assert(pl->current->pl == pl); + if (direction < 0) + return pl->current->prev; + return pl->current_was_replaced ? pl->current : pl->current->next; +} + +void playlist_add_base_path(struct playlist *pl, bstr base_path) +{ + if (base_path.len == 0 || bstrcmp0(base_path, ".") == 0) + return; + for (struct playlist_entry *e = pl->first; e; e = e->next) { + if (!mp_is_url(bstr0(e->filename))) { + char *new_file = mp_path_join(e, base_path, bstr0(e->filename)); + talloc_free(e->filename); + e->filename = new_file; + } + } +} + +// Move all entries from source_pl to pl, appending them after the current entry +// of pl. source_pl will be empty, and all entries have changed ownership to pl. +void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl) +{ + struct playlist_entry *add_after = pl->current; + if (pl->current && pl->current_was_replaced) + add_after = pl->current->next; + if (!add_after) + add_after = pl->last; + + while (source_pl->first) { + struct playlist_entry *e = source_pl->first; + playlist_unlink(source_pl, e); + playlist_insert(pl, add_after, e); + add_after = e; + } +} + +// Return number of entries between list start and e. +// Return -1 if e is not on the list, or if e is NULL. +int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e) +{ + struct playlist_entry *cur = pl->first; + int pos = 0; + if (!e) + return -1; + while (cur && cur != e) { + cur = cur->next; + pos++; + } + return cur == e ? pos : -1; +} + +int playlist_entry_count(struct playlist *pl) +{ + return playlist_entry_to_index(pl, pl->last) + 1; +} + +// Return entry for which playlist_entry_to_index() would return index. +// Return NULL if not found. +struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index) +{ + struct playlist_entry *e = pl->first; + for (int n = 0; ; n++) { + if (!e || n == index) + return e; + e = e->next; + } +} + diff --git a/common/playlist.h b/common/playlist.h new file mode 100644 index 0000000000..f383a85fea --- /dev/null +++ b/common/playlist.h @@ -0,0 +1,82 @@ +/* + * This file is part of mplayer. + * + * mplayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * mplayer 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 mplayer. If not, see <http://www.gnu.org/licenses/>. + */ + +#ifndef MPLAYER_PLAYLIST_H +#define MPLAYER_PLAYLIST_H + +#include <stdbool.h> +#include "bstr/bstr.h" + +struct playlist_param { + bstr name, value; +}; + +struct playlist_entry { + struct playlist_entry *prev, *next; + struct playlist *pl; + + char *filename; + + struct playlist_param *params; + int num_params; + + // Set to true if playback didn't seem to work, or if the file could be + // played only for a very short time. This is used to make playlist + // navigation just work in case the user has unplayable files in the + // playlist. + bool playback_short : 1; + // Set to true if not at least 1 frame (audio or video) could be played. + bool init_failed : 1; +}; + +struct playlist { + struct playlist_entry *first, *last; + + // This provides some sort of stable iterator. If this entry is removed from + // the playlist, current is set to the next element (or NULL), and + // current_was_replaced is set to true. + struct playlist_entry *current; + bool current_was_replaced; +}; + +void playlist_entry_add_param(struct playlist_entry *e, bstr name, bstr value); +void playlist_entry_add_params(struct playlist_entry *e, + struct playlist_param *params, + int params_count); + +struct playlist_entry *playlist_entry_new(const char *filename); + +void playlist_insert(struct playlist *pl, struct playlist_entry *after, + struct playlist_entry *add); +void playlist_add(struct playlist *pl, struct playlist_entry *add); +void playlist_remove(struct playlist *pl, struct playlist_entry *entry); +void playlist_clear(struct playlist *pl); + +void playlist_move(struct playlist *pl, struct playlist_entry *entry, + struct playlist_entry *at); + +void playlist_add_file(struct playlist *pl, const char *filename); +void playlist_shuffle(struct playlist *pl); +struct playlist_entry *playlist_get_next(struct playlist *pl, int direction); +void playlist_add_base_path(struct playlist *pl, bstr base_path); +void playlist_transfer_entries(struct playlist *pl, struct playlist *source_pl); + +int playlist_entry_to_index(struct playlist *pl, struct playlist_entry *e); +int playlist_entry_count(struct playlist *pl); +struct playlist_entry *playlist_entry_from_index(struct playlist *pl, int index); + +#endif diff --git a/common/playlist_parser.c b/common/playlist_parser.c new file mode 100644 index 0000000000..af249f4aa5 --- /dev/null +++ b/common/playlist_parser.c @@ -0,0 +1,566 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +/* + * Warning: this is outdated, crappy code. It is used only for --playlist. + * New or cleaned up code should be added to demux_playlist.c instead. + */ + +#include "config.h" +#include <stdlib.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> +#include <errno.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <unistd.h> +#include <ctype.h> +#include <limits.h> + +#include "talloc.h" +#include "asxparser.h" +#include "playlist.h" +#include "playlist_parser.h" +#include "stream/stream.h" +#include "demux/demux.h" +#include "common/msg.h" +#include "options/path.h" + + +#define BUF_STEP 1024 + +#define WHITES " \n\r\t" + +typedef struct play_tree_parser { + struct stream *stream; + char *buffer,*iter,*line; + int buffer_size , buffer_end; + int keep; + struct playlist *pl; +} play_tree_parser_t; + +static void +strstrip(char* str) { + char* i; + + if (str==NULL) + return; + for(i = str ; i[0] != '\0' && strchr(WHITES,i[0]) != NULL; i++) + /* NOTHING */; + if(i[0] != '\0') { + memmove(str,i,strlen(i) + 1); + for(i = str + strlen(str) - 1 ; strchr(WHITES,i[0]) != NULL; i--) + /* NOTHING */; + i[1] = '\0'; + } else + str[0] = '\0'; +} + +static char* +play_tree_parser_get_line(play_tree_parser_t* p) { + char *end,*line_end; + int r,resize = 0; + + if(p->buffer == NULL) { + p->buffer = malloc(BUF_STEP); + p->buffer_size = BUF_STEP; + p->buffer[0] = 0; + p->iter = p->buffer; + } + + if(p->stream->eof && (p->buffer_end == 0 || p->iter[0] == '\0')) + return NULL; + + assert(p->buffer_end < p->buffer_size); + assert(!p->buffer[p->buffer_end]); + while(1) { + + if(resize) { + char *tmp; + r = p->iter - p->buffer; + end = p->buffer + p->buffer_end; + if (p->buffer_size > INT_MAX - BUF_STEP) + break; + tmp = realloc(p->buffer, p->buffer_size + BUF_STEP); + if (!tmp) + break; + p->buffer = tmp; + p->iter = p->buffer + r; + p->buffer_size += BUF_STEP; + resize = 0; + } + + if(p->buffer_size - p->buffer_end > 1 && ! p->stream->eof) { + r = stream_read(p->stream,p->buffer + p->buffer_end,p->buffer_size - p->buffer_end - 1); + if(r > 0) { + p->buffer_end += r; + assert(p->buffer_end < p->buffer_size); + p->buffer[p->buffer_end] = '\0'; + while(strlen(p->buffer + p->buffer_end - r) != r) + p->buffer[p->buffer_end - r + strlen(p->buffer + p->buffer_end - r)] = '\n'; + } + assert(!p->buffer[p->buffer_end]); + } + + end = strchr(p->iter,'\n'); + if(!end) { + if(p->stream->eof) { + end = p->buffer + p->buffer_end; + break; + } + resize = 1; + continue; + } + break; + } + + line_end = (end > p->iter && *(end-1) == '\r') ? end-1 : end; + if(line_end - p->iter >= 0) + p->line = realloc(p->line, line_end - p->iter + 1); + else + return NULL; + if(line_end - p->iter > 0) + strncpy(p->line,p->iter,line_end - p->iter); + p->line[line_end - p->iter] = '\0'; + if(end[0] != '\0') + end++; + + if(!p->keep) { + if(end[0] != '\0') { + p->buffer_end -= end-p->iter; + memmove(p->buffer,end,p->buffer_end); + } else + p->buffer_end = 0; + p->buffer[p->buffer_end] = '\0'; + p->iter = p->buffer; + } else + p->iter = end; + + return p->line; +} + +static void +play_tree_parser_reset(play_tree_parser_t* p) { + p->iter = p->buffer; +} + +static void +play_tree_parser_stop_keeping(play_tree_parser_t* p) { + p->keep = 0; + if(p->iter && p->iter != p->buffer) { + p->buffer_end -= p->iter -p->buffer; + if(p->buffer_end) + memmove(p->buffer,p->iter,p->buffer_end); + p->buffer[p->buffer_end] = 0; + p->iter = p->buffer; + } +} + + +static bool parse_asx(play_tree_parser_t* p) { + int comments = 0,get_line = 1; + char* line = NULL; + + mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying asx...\n"); + + while(1) { + if(get_line) { + line = play_tree_parser_get_line(p); + if(!line) + return false; + strstrip(line); + if(line[0] == '\0') + continue; + } + if(!comments) { + if(line[0] != '<') { + mp_msg(MSGT_PLAYTREE,MSGL_DBG2,"First char isn't '<' but '%c'\n",line[0]); + mp_msg(MSGT_PLAYTREE,MSGL_DBG3,"Buffer = [%s]\n",p->buffer); + return false; + } else if(strncmp(line,"<!--",4) == 0) { // Comments + comments = 1; + line += 4; + if(line[0] != '\0' && strlen(line) > 0) + get_line = 0; + } else if(strncasecmp(line,"<ASX",4) == 0) // We got an asx element + break; + else // We don't get an asx + return false; + } else { // Comments + char* c; + c = strchr(line,'-'); + if(c) { + if (strncmp(c,"--!>",4) == 0) { // End of comments + comments = 0; + line = c+4; + if(line[0] != '\0') // There is some more data on this line : keep it + get_line = 0; + + } else { + line = c+1; // Jump the - + if(line[0] != '\0') // Some more data + get_line = 0; + else // End of line + get_line = 1; + } + } else // No - on this line (or rest of line) : get next one + get_line = 1; + } + } + + mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected asx format\n"); + + // We have an asx : load it in memory and parse + + while((line = play_tree_parser_get_line(p)) != NULL) + /* NOTHING */; + + mp_msg(MSGT_PLAYTREE,MSGL_DBG3,"Parsing asx file: [%s]\n",p->buffer); + return asx_parse(p->buffer,p->pl); +} + +static bool parse_smil(play_tree_parser_t* p) { + int entrymode=0; + char* line,source[512],*pos,*s_start,*s_end,*src_line; + int is_rmsmil = 0; + unsigned int npkt, ttlpkt; + + mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying smil playlist...\n"); + + // Check if smil + while((line = play_tree_parser_get_line(p)) != NULL) { + strstrip(line); + if(line[0] == '\0') // Ignore empties + continue; + if (strncasecmp(line,"<?xml",5)==0) // smil in xml + continue; + if (strncasecmp(line,"<!DOCTYPE smil",13)==0) // smil in xml + continue; + if (strncasecmp(line,"<smil",5)==0 || strncasecmp(line,"<?wpl",5)==0 || + strncasecmp(line,"(smil-document",14)==0) + break; // smil header found + else + return NULL; //line not smil exit + } + + if (!line) return NULL; + mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected smil playlist format\n"); + play_tree_parser_stop_keeping(p); + + if (strncasecmp(line,"(smil-document",14)==0) { + mp_msg(MSGT_PLAYTREE,MSGL_V,"Special smil-over-realrtsp playlist header\n"); + is_rmsmil = 1; + if (sscanf(line, "(smil-document (ver 1.0)(npkt %u)(ttlpkt %u", &npkt, &ttlpkt) != 2) { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: header parsing failure, assuming single packet.\n"); + npkt = ttlpkt = 1; + } + if (ttlpkt == 0 || npkt > ttlpkt) { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: bad packet counters (npkk = %u, ttlpkt = %u), assuming single packet.\n", + npkt, ttlpkt); + npkt = ttlpkt = 1; + } + } + + //Get entries from smil + src_line = line; + line = NULL; + do { + strstrip(src_line); + free(line); + line = NULL; + /* If we're parsing smil over realrtsp and this is not the last packet and + * this is the last line in the packet (terminating with ") ) we must get + * the next line, strip the header, and concatenate it to the current line. + */ + if (is_rmsmil && npkt != ttlpkt && strstr(src_line,"\")")) { + char *payload; + + line = strdup(src_line); + if(!(src_line = play_tree_parser_get_line(p))) { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: can't get line from packet %u/%u.\n", npkt, ttlpkt); + break; + } + strstrip(src_line); + // Skip header, packet starts after " + if(!(payload = strchr(src_line,'\"'))) { + mp_msg(MSGT_PLAYTREE,MSGL_WARN,"smil-over-realrtsp: can't find start of packet, using complete line.\n"); + payload = src_line; + } else + payload++; + // Skip ") at the end of the last line from the current packet + line[strlen(line)-2] = 0; + line = realloc(line, strlen(line)+strlen(payload)+1); + strcat (line, payload); + npkt++; + } else + line = strdup(src_line); + /* Unescape \" to " for smil-over-rtsp */ + if (is_rmsmil && line[0] != '\0') { + int i, j; + + for (i = 0; i < strlen(line); i++) + if (line[i] == '\\' && line[i+1] == '"') + for (j = i; line[j]; j++) + line[j] = line[j+1]; + } + pos = line; + while (pos) { + if (!entrymode) { // all entries filled so far + while ((pos=strchr(pos, '<'))) { + if (strncasecmp(pos,"<video",6)==0 || strncasecmp(pos,"<audio",6)==0 || strncasecmp(pos,"<media",6)==0) { + entrymode=1; + break; // Got a valid tag, exit '<' search loop + } + pos++; + } + } + if (entrymode) { //Entry found but not yet filled + pos = strstr(pos,"src="); // Is source present on this line + if (pos != NULL) { + entrymode=0; + if (pos[4] != '"' && pos[4] != '\'') { + mp_msg(MSGT_PLAYTREE,MSGL_V,"Unknown delimiter %c in source line %s\n", pos[4], line); + break; + } + s_start=pos+5; + s_end=strchr(s_start,pos[4]); + if (s_end == NULL) { + mp_msg(MSGT_PLAYTREE,MSGL_V,"Error parsing this source line %s\n",line); + break; + } + if (s_end-s_start> 511) { + mp_msg(MSGT_PLAYTREE,MSGL_V,"Cannot store such a large source %s\n",line); + break; + } + strncpy(source,s_start,s_end-s_start); + source[(s_end-s_start)]='\0'; // Null terminate + playlist_add_file(p->pl, source); + pos = s_end; + } + } + } + } while((src_line = play_tree_parser_get_line(p)) != NULL); + + free(line); + return true; +} + +static bool parse_textplain(play_tree_parser_t* p) { + char* line; + + mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying plaintext playlist...\n"); + play_tree_parser_stop_keeping(p); + + while((line = play_tree_parser_get_line(p)) != NULL) { + strstrip(line); + if(line[0] == '\0' || line[0] == '#' || (line[0] == '/' && line[1] == '/')) + continue; + + playlist_add_file(p->pl,line); + } + + return true; +} + +/** + * \brief decode the base64 used in nsc files + * \param in input string, 0-terminated + * \param buf output buffer, must point to memory suitable for realloc, + * will be NULL on failure. + * \return decoded length in bytes + */ +static int decode_nsc_base64(char *in, char **buf) { + int i, j, n; + if (in[0] != '0' || in[1] != '2') + goto err_out; + in += 2; // skip prefix + if (strlen(in) < 16) // error out if nothing to decode + goto err_out; + in += 12; // skip encoded string length + n = strlen(in) / 4; + *buf = realloc(*buf, n * 3); + for (i = 0; i < n; i++) { + uint8_t c[4]; + for (j = 0; j < 4; j++) { + c[j] = in[4 * i + j]; + if (c[j] >= '0' && c[j] <= '9') c[j] += 0 - '0'; + else if (c[j] >= 'A' && c[j] <= 'Z') c[j] += 10 - 'A'; + else if (c[j] >= 'a' && c[j] <= 'z') c[j] += 36 - 'a'; + else if (c[j] == '{') c[j] = 62; + else if (c[j] == '}') c[j] = 63; + else { + mp_msg(MSGT_PLAYTREE, MSGL_ERR, "Invalid character %c (0x%02"PRIx8")\n", c[j], c[j]); + goto err_out; + } + } + (*buf)[3 * i] = (c[0] << 2) | (c[1] >> 4); + (*buf)[3 * i + 1] = (c[1] << 4) | (c[2] >> 2); + (*buf)[3 * i + 2] = (c[2] << 6) | c[3]; + } + return 3 * n; +err_out: + free(*buf); + *buf = NULL; + return 0; +} + +/** + * \brief "converts" utf16 to ascii by just discarding every second byte + * \param buf buffer to convert + * \param len lenght of buffer, must be > 0 + */ +static void utf16_to_ascii(char *buf, int len) { + int i; + if (len <= 0) return; + for (i = 0; i < len / 2; i++) + buf[i] = buf[i * 2]; + buf[i] = 0; // just in case +} + +static bool parse_nsc(play_tree_parser_t* p) { + char *line, *addr = NULL, *url, *unicast_url = NULL; + int port = 0; + + mp_msg(MSGT_PLAYTREE,MSGL_V,"Trying nsc playlist...\n"); + while((line = play_tree_parser_get_line(p)) != NULL) { + strstrip(line); + if(!line[0]) // Ignore empties + continue; + if (strncasecmp(line,"[Address]", 9) == 0) + break; // nsc header found + else + return false; + } + mp_msg(MSGT_PLAYTREE,MSGL_V,"Detected nsc playlist format\n"); + play_tree_parser_stop_keeping(p); + while ((line = play_tree_parser_get_line(p)) != NULL) { + strstrip(line); + if (!line[0]) + continue; + if (strncasecmp(line, "Unicast URL=", 12) == 0) { + int len = decode_nsc_base64(&line[12], &unicast_url); + if (len <= 0) + mp_msg(MSGT_PLAYTREE, MSGL_WARN, "[nsc] Unsupported Unicast URL encoding\n"); + else + utf16_to_ascii(unicast_url, len); + } else if (strncasecmp(line, "IP Address=", 11) == 0) { + int len = decode_nsc_base64(&line[11], &addr); + if (len <= 0) + mp_msg(MSGT_PLAYTREE, MSGL_WARN, "[nsc] Unsupported IP Address encoding\n"); + else + utf16_to_ascii(addr, len); + } else if (strncasecmp(line, "IP Port=", 8) == 0) { + port = strtol(&line[8], NULL, 0); + } + } + + bool success = false; + + if (unicast_url) + url = strdup(unicast_url); + else if (addr && port) { + url = malloc(strlen(addr) + 7 + 20 + 1); + sprintf(url, "http://%s:%i", addr, port); + } else + goto err_out; + + playlist_add_file(p->pl, url); + free(url); + success = true; +err_out: + free(addr); + free(unicast_url); + return success; +} + +static struct playlist *do_parse(struct stream* stream, bool forced); + +struct playlist *playlist_parse_file(const char *file, struct MPOpts *opts) +{ + stream_t *stream = stream_open(file, opts); + if(!stream) { + mp_msg(MSGT_PLAYTREE,MSGL_ERR, + "Error while opening playlist file %s: %s\n", + file, strerror(errno)); + return false; + } + + mp_msg(MSGT_PLAYTREE, MSGL_V, + "Parsing playlist file %s...\n", file); + + struct playlist *ret = do_parse(stream, true); + free_stream(stream); + + if (ret) + playlist_add_base_path(ret, mp_dirname(file)); + + return ret; + +} + +typedef bool (*parser_fn)(play_tree_parser_t *); +static const parser_fn pl_parsers[] = { + parse_asx, + parse_smil, + parse_nsc, + parse_textplain +}; + + +static struct playlist *do_parse(struct stream* stream, bool forced) +{ + play_tree_parser_t p = { + .stream = stream, + .pl = talloc_zero(NULL, struct playlist), + .keep = 1, + }; + + bool success = false; + struct demuxer *pl_demux = demux_open(stream, "playlist", NULL, stream->opts); + if (pl_demux && pl_demux->playlist) { + playlist_transfer_entries(p.pl, pl_demux->playlist); + success = true; + } + free_demuxer(pl_demux); + if (!success && play_tree_parser_get_line(&p) != NULL) { + for (int n = 0; n < sizeof(pl_parsers) / sizeof(pl_parsers[0]); n++) { + play_tree_parser_reset(&p); + if (pl_parsers[n] == parse_textplain && !forced) + break; + if (pl_parsers[n](&p)) { + success = true; + break; + } + } + } + + if(success) + mp_msg(MSGT_PLAYTREE,MSGL_V,"Playlist successfully parsed\n"); + else { + mp_msg(MSGT_PLAYTREE,((forced==1)?MSGL_ERR:MSGL_V),"Error while parsing playlist\n"); + talloc_free(p.pl); + p.pl = NULL; + } + + if (p.pl && !p.pl->first) + mp_msg(MSGT_PLAYTREE,((forced==1)?MSGL_WARN:MSGL_V),"Warning: empty playlist\n"); + + return p.pl; +} diff --git a/common/playlist_parser.h b/common/playlist_parser.h new file mode 100644 index 0000000000..a541aa2cb4 --- /dev/null +++ b/common/playlist_parser.h @@ -0,0 +1,29 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#ifndef MPLAYER_PLAYLISTPARSER_H +#define MPLAYER_PLAYLISTPARSER_H + +#include <stdbool.h> + +struct MPOpts; +struct playlist; + +struct playlist *playlist_parse_file(const char *file, struct MPOpts *opts); + +#endif diff --git a/common/version.c b/common/version.c new file mode 100644 index 0000000000..23a0c59bc3 --- /dev/null +++ b/common/version.c @@ -0,0 +1,26 @@ +/* + * This file is part of MPlayer. + * + * MPlayer is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * MPlayer 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 MPlayer; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include "version.h" +#ifdef NO_BUILD_TIMESTAMPS +#undef BUILDDATE +#define BUILDDATE "UNKNOWN" +#endif + +const char *mplayer_version = "mpv " VERSION; +const char *mplayer_builddate = BUILDDATE; |