| /* | |
| * various utility functions for use within FFmpeg | |
| * Copyright (c) 2000, 2001, 2002 Fabrice Bellard | |
| * | |
| * This file is part of FFmpeg. | |
| * | |
| * FFmpeg is free software; you can redistribute it and/or | |
| * modify it under the terms of the GNU Lesser General Public | |
| * License as published by the Free Software Foundation; either | |
| * version 2.1 of the License, or (at your option) any later version. | |
| * | |
| * FFmpeg is distributed in the hope that it will be useful, | |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | |
| * Lesser General Public License for more details. | |
| * | |
| * You should have received a copy of the GNU Lesser General Public | |
| * License along with FFmpeg; if not, write to the Free Software | |
| * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | |
| */ | |
| #include <stdint.h> | |
| #include "config.h" | |
| #include "libavutil/avstring.h" | |
| #include "libavutil/bprint.h" | |
| #include "libavutil/internal.h" | |
| #include "libavutil/thread.h" | |
| #include "libavutil/time.h" | |
| #include "libavcodec/internal.h" | |
| #include "avformat.h" | |
| #include "avio_internal.h" | |
| #include "internal.h" | |
| #if CONFIG_NETWORK | |
| #include "network.h" | |
| #endif | |
| #include "os_support.h" | |
| static AVMutex avformat_mutex = AV_MUTEX_INITIALIZER; | |
| /** | |
| * @file | |
| * various utility functions for use within FFmpeg | |
| */ | |
| int ff_lock_avformat(void) | |
| { | |
| return ff_mutex_lock(&avformat_mutex) ? -1 : 0; | |
| } | |
| int ff_unlock_avformat(void) | |
| { | |
| return ff_mutex_unlock(&avformat_mutex) ? -1 : 0; | |
| } | |
| /* an arbitrarily chosen "sane" max packet size -- 50M */ | |
| #define SANE_CHUNK_SIZE (50000000) | |
| /* Read the data in sane-sized chunks and append to pkt. | |
| * Return the number of bytes read or an error. */ | |
| static int append_packet_chunked(AVIOContext *s, AVPacket *pkt, int size) | |
| { | |
| int orig_size = pkt->size; | |
| int ret; | |
| do { | |
| int prev_size = pkt->size; | |
| int read_size; | |
| /* When the caller requests a lot of data, limit it to the amount | |
| * left in file or SANE_CHUNK_SIZE when it is not known. */ | |
| read_size = size; | |
| if (read_size > SANE_CHUNK_SIZE/10) { | |
| read_size = ffio_limit(s, read_size); | |
| // If filesize/maxsize is unknown, limit to SANE_CHUNK_SIZE | |
| if (ffiocontext(s)->maxsize < 0) | |
| read_size = FFMIN(read_size, SANE_CHUNK_SIZE); | |
| } | |
| ret = av_grow_packet(pkt, read_size); | |
| if (ret < 0) | |
| break; | |
| ret = avio_read(s, pkt->data + prev_size, read_size); | |
| if (ret != read_size) { | |
| av_shrink_packet(pkt, prev_size + FFMAX(ret, 0)); | |
| break; | |
| } | |
| size -= read_size; | |
| } while (size > 0); | |
| if (size > 0) | |
| pkt->flags |= AV_PKT_FLAG_CORRUPT; | |
| if (!pkt->size) | |
| av_packet_unref(pkt); | |
| return pkt->size > orig_size ? pkt->size - orig_size : ret; | |
| } | |
| int av_get_packet(AVIOContext *s, AVPacket *pkt, int size) | |
| { | |
| #if FF_API_INIT_PACKET | |
| FF_DISABLE_DEPRECATION_WARNINGS | |
| av_init_packet(pkt); | |
| pkt->data = NULL; | |
| pkt->size = 0; | |
| FF_ENABLE_DEPRECATION_WARNINGS | |
| #else | |
| av_packet_unref(pkt); | |
| #endif | |
| pkt->pos = avio_tell(s); | |
| return append_packet_chunked(s, pkt, size); | |
| } | |
| int av_append_packet(AVIOContext *s, AVPacket *pkt, int size) | |
| { | |
| if (!pkt->size) | |
| return av_get_packet(s, pkt, size); | |
| return append_packet_chunked(s, pkt, size); | |
| } | |
| int av_filename_number_test(const char *filename) | |
| { | |
| char buf[1024]; | |
| return filename && | |
| (av_get_frame_filename(buf, sizeof(buf), filename, 1) >= 0); | |
| } | |
| /**********************************************************/ | |
| unsigned int ff_codec_get_tag(const AVCodecTag *tags, enum AVCodecID id) | |
| { | |
| while (tags->id != AV_CODEC_ID_NONE) { | |
| if (tags->id == id) | |
| return tags->tag; | |
| tags++; | |
| } | |
| return 0; | |
| } | |
| enum AVCodecID ff_codec_get_id(const AVCodecTag *tags, unsigned int tag) | |
| { | |
| for (int i = 0; tags[i].id != AV_CODEC_ID_NONE; i++) | |
| if (tag == tags[i].tag) | |
| return tags[i].id; | |
| for (int i = 0; tags[i].id != AV_CODEC_ID_NONE; i++) | |
| if (ff_toupper4(tag) == ff_toupper4(tags[i].tag)) | |
| return tags[i].id; | |
| return AV_CODEC_ID_NONE; | |
| } | |
| enum AVCodecID ff_get_pcm_codec_id(int bps, int flt, int be, int sflags) | |
| { | |
| if (bps <= 0 || bps > 64) | |
| return AV_CODEC_ID_NONE; | |
| if (flt) { | |
| switch (bps) { | |
| case 32: | |
| return be ? AV_CODEC_ID_PCM_F32BE : AV_CODEC_ID_PCM_F32LE; | |
| case 64: | |
| return be ? AV_CODEC_ID_PCM_F64BE : AV_CODEC_ID_PCM_F64LE; | |
| default: | |
| return AV_CODEC_ID_NONE; | |
| } | |
| } else { | |
| bps += 7; | |
| bps >>= 3; | |
| if (sflags & (1 << (bps - 1))) { | |
| switch (bps) { | |
| case 1: | |
| return AV_CODEC_ID_PCM_S8; | |
| case 2: | |
| return be ? AV_CODEC_ID_PCM_S16BE : AV_CODEC_ID_PCM_S16LE; | |
| case 3: | |
| return be ? AV_CODEC_ID_PCM_S24BE : AV_CODEC_ID_PCM_S24LE; | |
| case 4: | |
| return be ? AV_CODEC_ID_PCM_S32BE : AV_CODEC_ID_PCM_S32LE; | |
| case 8: | |
| return be ? AV_CODEC_ID_PCM_S64BE : AV_CODEC_ID_PCM_S64LE; | |
| default: | |
| return AV_CODEC_ID_NONE; | |
| } | |
| } else { | |
| switch (bps) { | |
| case 1: | |
| return AV_CODEC_ID_PCM_U8; | |
| case 2: | |
| return be ? AV_CODEC_ID_PCM_U16BE : AV_CODEC_ID_PCM_U16LE; | |
| case 3: | |
| return be ? AV_CODEC_ID_PCM_U24BE : AV_CODEC_ID_PCM_U24LE; | |
| case 4: | |
| return be ? AV_CODEC_ID_PCM_U32BE : AV_CODEC_ID_PCM_U32LE; | |
| default: | |
| return AV_CODEC_ID_NONE; | |
| } | |
| } | |
| } | |
| } | |
| unsigned int av_codec_get_tag(const AVCodecTag *const *tags, enum AVCodecID id) | |
| { | |
| unsigned int tag; | |
| if (!av_codec_get_tag2(tags, id, &tag)) | |
| return 0; | |
| return tag; | |
| } | |
| int av_codec_get_tag2(const AVCodecTag * const *tags, enum AVCodecID id, | |
| unsigned int *tag) | |
| { | |
| for (int i = 0; tags && tags[i]; i++) { | |
| const AVCodecTag *codec_tags = tags[i]; | |
| while (codec_tags->id != AV_CODEC_ID_NONE) { | |
| if (codec_tags->id == id) { | |
| *tag = codec_tags->tag; | |
| return 1; | |
| } | |
| codec_tags++; | |
| } | |
| } | |
| return 0; | |
| } | |
| enum AVCodecID av_codec_get_id(const AVCodecTag *const *tags, unsigned int tag) | |
| { | |
| for (int i = 0; tags && tags[i]; i++) { | |
| enum AVCodecID id = ff_codec_get_id(tags[i], tag); | |
| if (id != AV_CODEC_ID_NONE) | |
| return id; | |
| } | |
| return AV_CODEC_ID_NONE; | |
| } | |
| int ff_alloc_extradata(AVCodecParameters *par, int size) | |
| { | |
| av_freep(&par->extradata); | |
| par->extradata_size = 0; | |
| if (size < 0 || size >= INT32_MAX - AV_INPUT_BUFFER_PADDING_SIZE) | |
| return AVERROR(EINVAL); | |
| par->extradata = av_malloc(size + AV_INPUT_BUFFER_PADDING_SIZE); | |
| if (!par->extradata) | |
| return AVERROR(ENOMEM); | |
| memset(par->extradata + size, 0, AV_INPUT_BUFFER_PADDING_SIZE); | |
| par->extradata_size = size; | |
| return 0; | |
| } | |
| /*******************************************************/ | |
| uint64_t ff_ntp_time(void) | |
| { | |
| return (av_gettime() / 1000) * 1000 + NTP_OFFSET_US; | |
| } | |
| uint64_t ff_get_formatted_ntp_time(uint64_t ntp_time_us) | |
| { | |
| uint64_t ntp_ts, frac_part, sec; | |
| uint32_t usec; | |
| //current ntp time in seconds and micro seconds | |
| sec = ntp_time_us / 1000000; | |
| usec = ntp_time_us % 1000000; | |
| //encoding in ntp timestamp format | |
| frac_part = usec * 0xFFFFFFFFULL; | |
| frac_part /= 1000000; | |
| if (sec > 0xFFFFFFFFULL) | |
| av_log(NULL, AV_LOG_WARNING, "NTP time format roll over detected\n"); | |
| ntp_ts = sec << 32; | |
| ntp_ts |= frac_part; | |
| return ntp_ts; | |
| } | |
| uint64_t ff_parse_ntp_time(uint64_t ntp_ts) | |
| { | |
| uint64_t sec = ntp_ts >> 32; | |
| uint64_t frac_part = ntp_ts & 0xFFFFFFFFULL; | |
| uint64_t usec = (frac_part * 1000000) / 0xFFFFFFFFULL; | |
| return (sec * 1000000) + usec; | |
| } | |
| int av_get_frame_filename2(char *buf, int buf_size, const char *path, int number, int flags) | |
| { | |
| const char *p; | |
| char *q, buf1[20], c; | |
| int nd, len, percentd_found; | |
| q = buf; | |
| p = path; | |
| percentd_found = 0; | |
| for (;;) { | |
| c = *p++; | |
| if (c == '\0') | |
| break; | |
| if (c == '%') { | |
| do { | |
| nd = 0; | |
| while (av_isdigit(*p)) { | |
| if (nd >= INT_MAX / 10 - 255) | |
| goto fail; | |
| nd = nd * 10 + *p++ - '0'; | |
| } | |
| c = *p++; | |
| } while (av_isdigit(c)); | |
| switch (c) { | |
| case '%': | |
| goto addchar; | |
| case 'd': | |
| if (!(flags & AV_FRAME_FILENAME_FLAGS_MULTIPLE) && percentd_found) | |
| goto fail; | |
| percentd_found = 1; | |
| if (number < 0) | |
| nd += 1; | |
| snprintf(buf1, sizeof(buf1), "%0*d", nd, number); | |
| len = strlen(buf1); | |
| if ((q - buf + len) > buf_size - 1) | |
| goto fail; | |
| memcpy(q, buf1, len); | |
| q += len; | |
| break; | |
| default: | |
| goto fail; | |
| } | |
| } else { | |
| addchar: | |
| if ((q - buf) < buf_size - 1) | |
| *q++ = c; | |
| } | |
| } | |
| if (!percentd_found) | |
| goto fail; | |
| *q = '\0'; | |
| return 0; | |
| fail: | |
| *q = '\0'; | |
| return -1; | |
| } | |
| int av_get_frame_filename(char *buf, int buf_size, const char *path, int number) | |
| { | |
| return av_get_frame_filename2(buf, buf_size, path, number, 0); | |
| } | |
| void av_url_split(char *proto, int proto_size, | |
| char *authorization, int authorization_size, | |
| char *hostname, int hostname_size, | |
| int *port_ptr, char *path, int path_size, const char *url) | |
| { | |
| const char *p, *ls, *at, *at2, *col, *brk; | |
| if (port_ptr) | |
| *port_ptr = -1; | |
| if (proto_size > 0) | |
| proto[0] = 0; | |
| if (authorization_size > 0) | |
| authorization[0] = 0; | |
| if (hostname_size > 0) | |
| hostname[0] = 0; | |
| if (path_size > 0) | |
| path[0] = 0; | |
| /* parse protocol */ | |
| if ((p = strchr(url, ':'))) { | |
| av_strlcpy(proto, url, FFMIN(proto_size, p + 1 - url)); | |
| p++; /* skip ':' */ | |
| if (*p == '/') | |
| p++; | |
| if (*p == '/') | |
| p++; | |
| } else { | |
| /* no protocol means plain filename */ | |
| av_strlcpy(path, url, path_size); | |
| return; | |
| } | |
| /* separate path from hostname */ | |
| ls = p + strcspn(p, "/?#"); | |
| av_strlcpy(path, ls, path_size); | |
| /* the rest is hostname, use that to parse auth/port */ | |
| if (ls != p) { | |
| /* authorization (user[:pass]@hostname) */ | |
| at2 = p; | |
| while ((at = strchr(p, '@')) && at < ls) { | |
| av_strlcpy(authorization, at2, | |
| FFMIN(authorization_size, at + 1 - at2)); | |
| p = at + 1; /* skip '@' */ | |
| } | |
| if (*p == '[' && (brk = strchr(p, ']')) && brk < ls) { | |
| /* [host]:port */ | |
| av_strlcpy(hostname, p + 1, | |
| FFMIN(hostname_size, brk - p)); | |
| if (brk[1] == ':' && port_ptr) | |
| *port_ptr = atoi(brk + 2); | |
| } else if ((col = strchr(p, ':')) && col < ls) { | |
| av_strlcpy(hostname, p, | |
| FFMIN(col + 1 - p, hostname_size)); | |
| if (port_ptr) | |
| *port_ptr = atoi(col + 1); | |
| } else | |
| av_strlcpy(hostname, p, | |
| FFMIN(ls + 1 - p, hostname_size)); | |
| } | |
| } | |
| int ff_mkdir_p(const char *path) | |
| { | |
| int ret = 0; | |
| char *temp = av_strdup(path); | |
| char *pos = temp; | |
| char tmp_ch = '\0'; | |
| if (!path || !temp) { | |
| return -1; | |
| } | |
| if (!av_strncasecmp(temp, "/", 1) || !av_strncasecmp(temp, "\\", 1)) { | |
| pos++; | |
| } else if (!av_strncasecmp(temp, "./", 2) || !av_strncasecmp(temp, ".\\", 2)) { | |
| pos += 2; | |
| } | |
| for ( ; *pos != '\0'; ++pos) { | |
| if (*pos == '/' || *pos == '\\') { | |
| tmp_ch = *pos; | |
| *pos = '\0'; | |
| ret = mkdir(temp, 0755); | |
| *pos = tmp_ch; | |
| } | |
| } | |
| if ((*(pos - 1) != '/') && (*(pos - 1) != '\\')) { | |
| ret = mkdir(temp, 0755); | |
| } | |
| av_free(temp); | |
| return ret; | |
| } | |
| char *ff_data_to_hex(char *buff, const uint8_t *src, int s, int lowercase) | |
| { | |
| static const char hex_table_uc[16] = { '0', '1', '2', '3', | |
| '4', '5', '6', '7', | |
| '8', '9', 'A', 'B', | |
| 'C', 'D', 'E', 'F' }; | |
| static const char hex_table_lc[16] = { '0', '1', '2', '3', | |
| '4', '5', '6', '7', | |
| '8', '9', 'a', 'b', | |
| 'c', 'd', 'e', 'f' }; | |
| const char *hex_table = lowercase ? hex_table_lc : hex_table_uc; | |
| for (int i = 0; i < s; i++) { | |
| buff[i * 2] = hex_table[src[i] >> 4]; | |
| buff[i * 2 + 1] = hex_table[src[i] & 0xF]; | |
| } | |
| buff[2 * s] = '\0'; | |
| return buff; | |
| } | |
| int ff_hex_to_data(uint8_t *data, const char *p) | |
| { | |
| int c, len, v; | |
| len = 0; | |
| v = 1; | |
| for (;;) { | |
| p += strspn(p, SPACE_CHARS); | |
| if (*p == '\0') | |
| break; | |
| c = av_toupper((unsigned char) *p++); | |
| if (c >= '0' && c <= '9') | |
| c = c - '0'; | |
| else if (c >= 'A' && c <= 'F') | |
| c = c - 'A' + 10; | |
| else | |
| break; | |
| v = (v << 4) | c; | |
| if (v & 0x100) { | |
| if (data) | |
| data[len] = v; | |
| len++; | |
| v = 1; | |
| } | |
| } | |
| return len; | |
| } | |
| void ff_parse_key_value(const char *str, ff_parse_key_val_cb callback_get_buf, | |
| void *context) | |
| { | |
| const char *ptr = str; | |
| /* Parse key=value pairs. */ | |
| for (;;) { | |
| const char *key; | |
| char *dest = NULL, *dest_end; | |
| int key_len, dest_len = 0; | |
| /* Skip whitespace and potential commas. */ | |
| while (*ptr && (av_isspace(*ptr) || *ptr == ',')) | |
| ptr++; | |
| if (!*ptr) | |
| break; | |
| key = ptr; | |
| if (!(ptr = strchr(key, '='))) | |
| break; | |
| ptr++; | |
| key_len = ptr - key; | |
| callback_get_buf(context, key, key_len, &dest, &dest_len); | |
| dest_end = dest ? dest + dest_len - 1 : NULL; | |
| if (*ptr == '\"') { | |
| ptr++; | |
| while (*ptr && *ptr != '\"') { | |
| if (*ptr == '\\') { | |
| if (!ptr[1]) | |
| break; | |
| if (dest && dest < dest_end) | |
| *dest++ = ptr[1]; | |
| ptr += 2; | |
| } else { | |
| if (dest && dest < dest_end) | |
| *dest++ = *ptr; | |
| ptr++; | |
| } | |
| } | |
| if (*ptr == '\"') | |
| ptr++; | |
| } else { | |
| for (; *ptr && !(av_isspace(*ptr) || *ptr == ','); ptr++) | |
| if (dest && dest < dest_end) | |
| *dest++ = *ptr; | |
| } | |
| if (dest) | |
| *dest = 0; | |
| } | |
| } | |
| int avformat_network_init(void) | |
| { | |
| #if CONFIG_NETWORK | |
| int ret; | |
| if ((ret = ff_network_init()) < 0) | |
| return ret; | |
| if ((ret = ff_tls_init()) < 0) | |
| return ret; | |
| #endif | |
| return 0; | |
| } | |
| int avformat_network_deinit(void) | |
| { | |
| #if CONFIG_NETWORK | |
| ff_network_close(); | |
| ff_tls_deinit(); | |
| #endif | |
| return 0; | |
| } | |
| int ff_is_http_proto(const char *filename) { | |
| const char *proto = avio_find_protocol_name(filename); | |
| return proto ? (!av_strcasecmp(proto, "http") || !av_strcasecmp(proto, "https")) : 0; | |
| } | |
| int ff_bprint_to_codecpar_extradata(AVCodecParameters *par, struct AVBPrint *buf) | |
| { | |
| int ret; | |
| char *str; | |
| ret = av_bprint_finalize(buf, &str); | |
| if (ret < 0) | |
| return ret; | |
| if (!av_bprint_is_complete(buf)) { | |
| av_free(str); | |
| return AVERROR(ENOMEM); | |
| } | |
| par->extradata = str; | |
| /* Note: the string is NUL terminated (so extradata can be read as a | |
| * string), but the ending character is not accounted in the size (in | |
| * binary formats you are likely not supposed to mux that character). When | |
| * extradata is copied, it is also padded with AV_INPUT_BUFFER_PADDING_SIZE | |
| * zeros. */ | |
| par->extradata_size = buf->len; | |
| return 0; | |
| } |