El análisis anterior comprende los datos PCM y YUV de los datos sin procesar de audio y video generados por encapsulación, decodificación y decodificación. Hoy, hablemos de la operación inversa de desencapsulación: encapsulación .
¿Qué es la encapsulación de audio y video?
De hecho, es fácil comprender que la encapsulación de audio y video es en realidad un paquete de secuencias de audio y video, además de información adicional para su uso posterior.
Por ejemplo, para los datos pcm mencionados anteriormente, si desea reproducirlos, debe proporcionar parámetros como la frecuencia de muestreo, la cantidad de canales y el formato de muestreo; de lo contrario, no se puede reproducir. Y si se trata de un archivo de audio empaquetado, como un archivo mp3, puede ingresar directamente el nombre del archivo para reproducirlo.
Muxer de formato de paquete compatible con FFmpeg:
-
Línea de comando:
ffmpeg-muxers
-
Código fuente:
muxer_list.c: matriz de punteros muxer_list
La estructura correspondiente a mxer: AVOutputFormat
El proceso básico de embalaje:
-
Asignar salida AVFormatContext:
int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat,
const char *format_name, const char *filename);
-
Agregue un flujo de datos AVStream para el archivo de entrada:
AVStream representa un flujo de audio o video o subtítulos
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c)
-
Configure los parámetros relacionados con el código AVStream:
Configure el parámetro AVCodecParamters de AVStream;
-
Escribir en el encabezado del archivo :
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
Esta función llamará al método write_header del Muxer correspondiente.
-
Escribir datos en el archivo de salida :
av_interleaved_write_frame o av_write_frame
La diferencia entre los dos es que el primero almacena en caché y reorganiza el AVPacket de entrada para garantizar que la salida del AVPacket al archivo se incremente en dts. Este último no se almacena en caché y debe ser reorganizado por la persona que llama.
-
Escribir al final del archivo:
int av_write_trailer(AVFormatContext *s);
Esta función llama al método write_tailer de muxer.
-
Libere el AVFormatContext:
void avformat_free_context(AVFormatContext *s)
Nota: El prefijo para escribir la API del encabezado del archivo es avformat_, mientras que escribir los datos y la cola del archivo es av_.
Análisis de demostración oficial :
remuxing.c:
En este ejemplo, el archivo de entrada se desencapsula primero y luego se vuelve a empaquetar directamente, sin operaciones de decodificación y codificación.
Se ha introducido el artículo anterior sobre la operación de desencapsulado, y aquí sólo se analiza la parte de encapsulado.
int main(int argc, char **argv)
{
AVOutputFormat *ofmt = NULL;
AVFormatContext *ifmt_ctx = NULL, *ofmt_ctx = NULL;
AVPacket pkt;
const char *in_filename, *out_filename;
int ret, i;
int stream_index = 0;
int *stream_mapping = NULL;
int stream_mapping_size = 0;
if (argc < 3) {
printf("usage: %s input output\n"
"API example program to remux a media file with libavformat and libavcodec.\n"
"The output format is guessed according to the file extension.\n"
"\n", argv[0]);
return 1;
}
in_filename = argv[1];
out_filename = argv[2];
if ((ret = avformat_open_input(&ifmt_ctx, in_filename, 0, 0)) < 0) {
fprintf(stderr, "Could not open input file '%s'", in_filename);
goto end;
}
if ((ret = avformat_find_stream_info(ifmt_ctx, 0)) < 0) {
fprintf(stderr, "Failed to retrieve input stream information");
goto end;
}
av_dump_format(ifmt_ctx, 0, in_filename, 0);
//分配输出的AVFormatContext上下方
avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, out_filename);
if (!ofmt_ctx) {
fprintf(stderr, "Could not create output context\n");
ret = AVERROR_UNKNOWN;
goto end;
}
stream_mapping_size = ifmt_ctx->nb_streams;
stream_mapping = av_mallocz_array(stream_mapping_size, sizeof(*stream_mapping));
if (!stream_mapping) {
ret = AVERROR(ENOMEM);
goto end;
}
ofmt = ofmt_ctx->oformat;
for (i = 0; i < ifmt_ctx->nb_streams; i++) {
AVStream *out_stream;
AVStream *in_stream = ifmt_ctx->streams[i];
AVCodecParameters *in_codecpar = in_stream->codecpar;
if (in_codecpar->codec_type != AVMEDIA_TYPE_AUDIO &&
in_codecpar->codec_type != AVMEDIA_TYPE_VIDEO &&
in_codecpar->codec_type != AVMEDIA_TYPE_SUBTITLE) {
stream_mapping[i] = -1;
continue;
}
stream_mapping[i] = stream_index++;
//为输出添加音视频流AVStrem
out_stream = avformat_new_stream(ofmt_ctx, NULL);
if (!out_stream) {
fprintf(stderr, "Failed allocating output stream\n");
ret = AVERROR_UNKNOWN;
goto end;
}
//配置AVStream的AVCodecParameters,这里直接用的输入文件相应流的参数
ret = avcodec_parameters_copy(out_stream->codecpar, in_codecpar);
if (ret < 0) {
fprintf(stderr, "Failed to copy codec parameters\n");
goto end;
}
out_stream->codecpar->codec_tag = 0;
}
av_dump_format(ofmt_ctx, 0, out_filename, 1);
if (!(ofmt->flags & AVFMT_NOFILE)) {
ret = avio_open(&ofmt_ctx->pb, out_filename, AVIO_FLAG_WRITE);
if (ret < 0) {
fprintf(stderr, "Could not open output file '%s'", out_filename);
goto end;
}
}
//写入文件头
ret = avformat_write_header(ofmt_ctx, NULL);
if (ret < 0) {
fprintf(stderr, "Error occurred when opening output file\n");
goto end;
}
while (1) {
AVStream *in_stream, *out_stream;
ret = av_read_frame(ifmt_ctx, &pkt);
if (ret < 0)
break;
in_stream = ifmt_ctx->streams[pkt.stream_index];
if (pkt.stream_index >= stream_mapping_size ||
stream_mapping[pkt.stream_index] < 0) {
av_packet_unref(&pkt);
continue;
}
pkt.stream_index = stream_mapping[pkt.stream_index];
out_stream = ofmt_ctx->streams[pkt.stream_index];
log_packet(ifmt_ctx, &pkt, "in");
/* copy packet */
pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
pkt.pos = -1;
log_packet(ofmt_ctx, &pkt, "out");
//写入AVPacket数据
ret = av_interleaved_write_frame(ofmt_ctx, &pkt);
if (ret < 0) {
fprintf(stderr, "Error muxing packet\n");
break;
}
av_packet_unref(&pkt);
}
//写入文件尾
av_write_trailer(ofmt_ctx);
end:
avformat_close_input(&ifmt_ctx);
/* close output */
if (ofmt_ctx && !(ofmt->flags & AVFMT_NOFILE))
avio_closep(&ofmt_ctx->pb);
//释放输出AVFormatContext上下文。
avformat_free_context(ofmt_ctx);
av_freep(&stream_mapping);
if (ret < 0 && ret != AVERROR_EOF) {
fprintf(stderr, "Error occurred: %s\n", av_err2str(ret));
return 1;
}
return 0;
}
Una pequeña pregunta :
En el ejemplo oficial anterior, los datos de audio y los datos de video se escriben en forma cruzada. Si el video se escribe primero y luego el audio, ¿es posible?
Una vez un colega hizo esta pregunta y dijo que no hay problema con la verificación y que el audio y el video están sincronizados. Si buscas en Internet, también hay artículos que han sido probados y dicen que es posible.
¿Está realmente bien?
De hecho, esto está relacionado con el formato de encapsulación, en algunos formatos los datos de audio y video están intercalados, por ejemplo, habrá problemas con el flujo TS. Si los datos de audio y video se almacenan por separado, como mp4, no hay problema.
La herramienta se puede verificar modificando muxing.c en el código fuente de ffmpeg:
En primer lugar, cambie la definición de mascotas STEADM_duration de 10,0 a 1000,0. Este valor representa la duración total de los datos de audio y vídeo escritos.
Luego modifique la lógica original para escribir primero el video y luego el audio, como se muestra a continuación:
El cuadro rojo representa la lógica original y los dos bucles while siguientes se utilizan para escribir vídeo y audio respectivamente.
Ejecute el programa dos veces para generar ts stream y mp4 respectivamente, y luego use ffplay para verificar la reproducción:
La reproducción de mp4 es normal, pero la reproducción de ts stream solo tiene imágenes y no tiene sonido.