mirror of https://github.com/FFmpeg/FFmpeg.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
604 lines
16 KiB
604 lines
16 KiB
/* |
|
* 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 |
|
|
|
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; |
|
}
|
|
|