FFmpeg Programming Interface (API) Guide for Beginners
I. Building FFmpeg in Ubuntu
Add environment variable
echo "export LD_LIBRARY_PATH=/home/dong/2019-nCoV/_install/lib:$LD_LIBRARY_PATH">> ~/.bashrc
echo "export PKG_CONFIG_PATH=/home/dong/2019-nCoV/_install/lib/pkgconfig:$PKG_CONFIG_PATH">> ~/.bashrc
source ~/.bashrc
FFmpeg build
build.sh
./configure --prefix=/home/dong/2019-nCoV/_install --disable-x86asm
make
make install
ffmpeg + x264
# ffmpeg + x264 # ffmpeg ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static --enable-pic make && sudo make install # x264 ./configure --prefix=/home/dong/2019-nCoV/_install --enable-libx264 --enable-gpl --extra-cflags=-I/home/dong/2019-nCoV/_install/include --extra-cxxflags=-I/home/dong/2019-nCoV/_install/include --extra-ldflags=-L/home/dong/2019-nCoV/_install/lib make && sudo make install # build gcc muxing.c -o muxing `pkg-config --cflags --libs libavdevice libavformat libavfilter libavcodec libswresample libswscale libavutil` -lx264
FFmpeg + Third party Libraries
dong@ubuntu:~/2019-nCoV$ tree
.
├── build_centos.sh
├── build_ubuntu.sh
├── fdk-aac-2.0.0.tar.gz
├── ffmpeg-4.1.tar.bz2
├── ffmpeg-4.2.2.tar.bz2
├── lame-3.100.tar.gz
├── last_x264.tar.bz2
├── libogg-1.3.4.tar.gz
├── libvorbis-1.3.6.tar.gz
├── libvpx-1.8.0.tar.gz
├── opencore-amr-0.1.3.tar.gz
├── openssl-1.1.0f.tar.gz
├── SDL-1.2.15.tar.gz
├── SDL2-2.0.10.tar.gz
├── x265_2.9.tar.gz
└── xvidcore_1.3.3.orig.tar.gz
0 directories, 20 files
dong@ubuntu:~/2019-nCoV$
build_ubuntu.sh
#export LD_LIBRARY_PATH=/home/dong/2019-nCoV/_install/lib:$LD_LIBRARY_PATH #export PKG_CONFIG_PATH=/home/dong/2019-nCoV/_install/lib/pkgconfig:$PKG_CONFIG_PATH #echo "export LD_LIBRARY_PATH=/home/dong/2019-nCoV/_install/lib:$LD_LIBRARY_PATH">> ~/.bashrc #echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dong/2019-nCoV/_install/lib">> ~/.bashrc #echo "export PKG_CONFIG_PATH=/home/dong/2019-nCoV/_install/lib/pkgconfig:$PKG_CONFIG_PATH">> ~/.bashrc #echo "export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/home/dong/2019-nCoV/_install/lib/pkgconfig">> ~/.bashrc #source ~/.bashrc #sudo apt-get install yasm nasm cmake libx11-dev #1 tar xvf yasm-1.2.0.tar.gz cd yasm-1.2.0 ./configure #--prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #2 tar xvf nasm-2.13.03.tar.gz cd nasm-2.13.03 ./configure #--prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #3 tar xvf zlib-1.2.11.tar.gz cd zlib-1.2.11 ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared #--enable-static make && make install cd .. #4 tar xvf last_x264.tar.bz2 cd x264-snapshot-20190512-2245 ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static --enable-pic #--disable-asm make && make install cd .. #5 tar xvf x265_2.9.tar.gz cd x265_2.9/build/linux cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="/home/dong/2019-nCoV/_install" -DENABLE_SHARED:bool=on ../../source make make install cd ../../.. #6 tar xvf libvpx-1.8.0.tar.gz cd libvpx-1.8.0 ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #7 tar xvf fdk-aac-2.0.0.tar.gz cd fdk-aac-2.0.0 ./configure prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #8 tar xvf xvidcore_1.3.3.orig.tar.gz cd xvidcore-1.3.3/build/generic ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd ../../.. #9 tar xvf libogg-1.3.4.tar.gz cd libogg-1.3.4 ./configure prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #10 tar xvf libvorbis-1.3.6.tar.gz cd libvorbis-1.3.6 ./configure prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #11 tar xvf lame-3.100.tar.gz cd lame-3.100 ./configure prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #12 tar xvf opencore-amr-0.1.3.tar.gz cd opencore-amr-0.1.3 ./configure prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #13 tar xvf SDL-1.2.15.tar.gz cd SDL-1.2.15 sed -e '/_XData32/s:register long:register _Xconst long:' -i src/video/x11/SDL_x11sym.h ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --disable-static make && make install cd .. #14 tar xvf SDL2-2.0.10.tar.gz cd SDL2-2.0.10 ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make && make install cd .. #15 tar xvf ffmpeg-4.1.tar.bz2 cd ffmpeg-4.1 ./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static --enable-ffplay --enable-libx264 --enable-libx265 --enable-gpl --enable-libxvid --enable-libvpx --enable-libvorbis --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-version3 --enable-libfdk-aac --enable-nonfree --enable-postproc --enable-libxcb --disable-vaapi --extra-cflags=-I/home/dong/2019-nCoV/_install/include --extra-cxxflags=-I/home/dong/2019-nCoV/_install/include --extra-ldflags=-L/home/dong/2019-nCoV/_install/lib # make && make install cd ..
build_centos.sh
#export LD_LIBRARY_PATH=/home/dong/2019-nCoV/_install/lib:$LD_LIBRARY_PATH #export PKG_CONFIG_PATH=/home/dong/2019-nCoV/_install/lib/pkgconfig:$PKG_CONFIG_PATH #echo "export LD_LIBRARY_PATH=/home/dong/2019-nCoV/_install/lib:$LD_LIBRARY_PATH">> ~/.bashrc #echo "export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dong/2019-nCoV/_install/lib">> ~/.bashrc #echo "export PKG_CONFIG_PATH=/home/dong/2019-nCoV/_install/lib/pkgconfig:$PKG_CONFIG_PATH">> ~/.bashrc #echo "export PKG_CONFIG_PATH=$PKG_CONFIG_PATH:/home/dong/2019-nCoV/_install/lib/pkgconfig">> ~/.bashrc #source ~/.bashrc yum install libatomic.x86_64 yum install libxcb* yum install cmake #1 tar xvf yasm-1.2.0.tar.gz cd yasm-1.2.0 ./configure #--prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make sudo make install cd .. #2 tar xvf nasm-2.13.03.tar.gz cd nasm-2.13.03 ./configure #--prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static make sudo make install cd .. #3 tar xvf zlib-1.2.11.tar.gz cd zlib-1.2.11 ./configure --prefix=/usr/local/ffmpeg --enable-shared #--enable-static make && make install cd .. #4 tar xvf last_x264.tar.bz2 cd x264-snapshot-20190512-2245./configure --prefix=/home/dong/2019-nCoV/_install --enable-shared --enable-static --enable-pic #--disable-asm make && make install cd .. #5 tar xvf x265_2.9.tar.gz cd x265_2.9/build/linux cmake -G "Unix Makefiles" -DCMAKE_INSTALL_PREFIX="/home/dong/2019-nCoV/_install" -DENABLE_SHARED:bool=on ../../source make make install cd ../../.. #6 tar xvf libvpx-1.8.0.tar.gz cd libvpx-1.8.0 ./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #7 tar xvf fdk-aac-2.0.0.tar.gz cd fdk-aac-2.0.0 ./configure prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #8 tar xvf xvidcore_1.3.3.orig.tar.gz cd xvidcore-1.3.3/build/generic ./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd ../../.. #9 tar xvf libogg-1.3.4.tar.gz cd libogg-1.3.4 ./configure prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #10 tar xvf libvorbis-1.3.6.tar.gz cd libvorbis-1.3.6 ./configure prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #11 tar xvf lame-3.100.tar.gz cd lame-3.100 ./configure prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #12 tar xvf opencore-amr-0.1.3.tar.gz cd opencore-amr-0.1.3 ./configure prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #13 不需要sdl-1.xx版本了 #tar xvf SDL-1.2.15.tar.gz #cd SDL-1.2.15 #sed -e '/_XData32/s:register long:register _Xconst long:' -i src/video/x11/SDL_x11sym.h #./configure --prefix=/usr/local/ffmpeg --enable-shared --disable-static #make && make install #cd .. #14 tar xvf SDL2-2.0.10.tar.gz cd SDL2-2.0.10 ./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-static make && make install cd .. #15 tar xvf ffmpeg-4.1.tar.bz2 cd ffmpeg-4.1 ./configure --prefix=/usr/local/ffmpeg --enable-shared --enable-static --enable-ffplay --enable-libx264 --enable-libx265 --enable-gpl --enable-libxvid --enable-libvpx --enable-libvorbis --enable-libmp3lame --enable-libopencore-amrnb --enable-libopencore-amrwb --enable-version3 --enable-libfdk-aac --enable-nonfree --enable-postproc --enable-libxcb --disable-vaapi --extra-cflags=-I/usr/local/ffmpeg/include --extra-cxxflags=-I/usr/local/ffmpeg/include --extra-ldflags=-L/usr/local/ffmpeg/lib make && make install cd ..
#bug1 https://blog.csdn.net/bdemq/article/details/104051151
#bug2 https://blog.csdn.net/tenorange/article/details/86627337
II. FFmpeg Example
dong@ubuntu:~/2019-nCoV/ffmpeg-4.1
make examples
dong@ubuntu:~/2019-nCoV/ffmpeg-4.1/doc/examples$ tree
.
├── avio_dir_cmd.c
├── avio_reading.c
├── decode_audio.c
├── decode_video.c
├── demuxing_decoding.c
├── encode_audio.c
├── encode_video.c
├── extract_mvs.c
├── filter_audio.c
├── filtering_audio.c
├── filtering_video.c
├── http_multiclient.c
├── hw_decode.c
├── Makefile
├── Makefile.example
├── metadata.c
├── muxing.c
├── qsvdec.c
├── README
├── remuxing.c
├── resampling_audio.c
├── scaling_video.c
├── transcode_aac.c
├── transcoding.c
├── vaapi_encode.c
└── vaapi_transcode.c
0 directories, 26 files
1. avio
1.0 Custom AVIOContext
ffmpeg-4.1/doc/examples/avio_reading.c
/* * Copyright (c) 2014 Stefano Sabatini * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * @file * libavformat AVIOContext API example. * * Make libavformat demuxer access media content through a custom * AVIOContext read callback. * @example avio_reading.c */ #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/file.h> struct buffer_data { uint8_t *ptr; size_t size; ///< size left in the buffer }; static int read_packet(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, bd->size); if (!buf_size) return AVERROR_EOF; printf("ptr:%p size:%zu\n", bd->ptr, bd->size); /* copy internal buffer data to buf */ memcpy(buf, bd->ptr, buf_size); bd->ptr += buf_size; bd->size -= buf_size; return buf_size; } int main(int argc, char *argv[]) { AVFormatContext *fmt_ctx = NULL; AVIOContext *avio_ctx = NULL; uint8_t *buffer = NULL, *avio_ctx_buffer = NULL; size_t buffer_size, avio_ctx_buffer_size = 4096; char *input_filename = NULL; int ret = 0; struct buffer_data bd = { 0 }; if (argc != 2) { fprintf(stderr, "usage: %s input_file\n" "API example program to show how to read from a custom buffer " "accessed through AVIOContext.\n", argv[0]); return 1; } input_filename = argv[1]; /* slurp file content into buffer */ ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL); if (ret < 0) goto end; /* fill opaque structure used by the AVIOContext read callback */ bd.ptr = buffer; bd.size = buffer_size; if (!(fmt_ctx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); goto end; } avio_ctx_buffer = av_malloc(avio_ctx_buffer_size); if (!avio_ctx_buffer) { ret = AVERROR(ENOMEM); goto end; } avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, NULL, NULL); if (!avio_ctx) { ret = AVERROR(ENOMEM); goto end; } fmt_ctx->pb = avio_ctx; ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL); if (ret < 0) { fprintf(stderr, "Could not open input\n"); goto end; } ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "Could not find stream information\n"); goto end; } av_dump_format(fmt_ctx, 0, input_filename, 0); end: avformat_close_input(&fmt_ctx); /* note: the internal buffer could have changed, and be != avio_ctx_buffer */ if (avio_ctx) { av_freep(&avio_ctx->buffer); av_freep(&avio_ctx); } av_file_unmap(buffer, buffer_size); if (ret < 0) { fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); return 1; } return 0; }
build.sh
export LD_LIBRARY_PATH=$(pwd)/_install/lib:$LD_LIBRARY_PATH #gcc -o transcoding transcoding.c \ gcc -o muxing muxing.c \ -I $(pwd) \ -I $(pwd)/_install/include \ -I $(pwd)/_install/include/libavcodec \ -I $(pwd)/_install/include/libavdevice \ -I $(pwd)/_install/include/libavfilter \ -I $(pwd)/_install/include/libavformat \ -I $(pwd)/_install/include/libavutil \ -I $(pwd)/_install/include/libpostproc \ -I $(pwd)/_install/include/libswresample \ -I $(pwd)/_install/include/libswscale \ -I $(pwd)/_install/include/libpostproc \ -I $(pwd)/_install/include/libyasm \ -I $(pwd)/_install/include/SDL \ -I $(pwd)/_install/include/SDL2 \ -L $(pwd)/_install/lib \ -Wno-deprecated-declarations -lx264 -lx265 -lSDL -lSDL2 -lavformat -lavutil -lavdevice -lavcodec -lswresample -lavfilter -lswscale -lpostproc -lz -lm -lpthread
dong@ubuntu:~/ffmpeg/example$ export LD_LIBRARY_PATH=$(pwd)/_install/lib:$LD_LIBRARY_PATH
dong@ubuntu:~/ffmpeg/example$ ./build.sh
dong@ubuntu:~/ffmpeg/example$ ls
avio_reading avio_reading.c build.sh _install source.200kbps.768x320.flv
dong@ubuntu:~/ffmpeg/example$ ./avio_reading source.200kbps.768x320.flv
ptr:0x7f59276ec000 size:6636853
ptr:0x7f59276ed000 size:6632757
ptr:0x7f59276ee000 size:6628661
ptr:0x7f59276ef000 size:6624565
ptr:0x7f59276f0000 size:6620469
ptr:0x7f59276f1000 size:6616373
ptr:0x7f59276f2000 size:6612277
ptr:0x7f59276f3000 size:6608181
Input #0, flv, from 'source.200kbps.768x320.flv':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41
encoder : Lavf54.63.104
Duration: 00:03:30.73, start: 0.034000, bitrate: N/A
Stream #0:0: Video: h264 (High), yuv420p(progressive), 768x320 [SAR 1:1 DAR 12:5], 212 kb/s, 25 fps, 25 tbr, 1k tbn, 50 tbc
Stream #0:1: Audio: aac (LC), 44100 Hz, stereo, fltp, 30 kb/s
dong@ubuntu:~/ffmpeg/example$
1.1 Custom AVIOContext vs File as Input Source
/* * Copyright (c) 2014 Stefano Sabatini * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * @file * libavformat AVIOContext API example. * * Make libavformat demuxer access media content through a custom * AVIOContext read callback. * @example avio_reading.c */ #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/file.h> #define Custom_AVIOContext (0) #if Custom_AVIOContext struct buffer_data { uint8_t *ptr; size_t size; ///< size left in the buffer }; static int read_packet(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, bd->size); if (!buf_size) return AVERROR_EOF; printf("ptr:%p size:%zu\n", bd->ptr, bd->size); /* copy internal buffer data to buf */ memcpy(buf, bd->ptr, buf_size); bd->ptr += buf_size; bd->size -= buf_size; return buf_size; } #endif int main(int argc, char *argv[]) { AVFormatContext *fmt_ctx = NULL; char *input_filename = argv[1]; int ret = 0; if (argc != 2) { fprintf(stderr, "usage: %s input_file\n" "API example program to show how to read from a custom buffer " "accessed through AVIOContext.\n", argv[0]); return 1; } #if Custom_AVIOContext AVIOContext *avio_ctx = NULL; uint8_t *buffer = NULL, *avio_ctx_buffer = NULL; size_t buffer_size, avio_ctx_buffer_size = 4096; struct buffer_data bd = { 0 }; /* slurp file content into buffer */ ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL); if (ret < 0) goto end; /* fill opaque structure used by the AVIOContext read callback */ bd.ptr = buffer; bd.size = buffer_size; if (!(fmt_ctx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); goto end; } avio_ctx_buffer = av_malloc(avio_ctx_buffer_size); if (!avio_ctx_buffer) { ret = AVERROR(ENOMEM); goto end; } avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, NULL, NULL); if (!avio_ctx) { ret = AVERROR(ENOMEM); goto end; } fmt_ctx->pb = avio_ctx; ret = avformat_open_input(&fmt_ctx, NULL, NULL, NULL); #else ret = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); #endif if (ret < 0) { fprintf(stderr, "Could not open input\n"); goto end; } ret = avformat_find_stream_info(fmt_ctx, NULL); if (ret < 0) { fprintf(stderr, "Could not find stream information\n"); goto end; } av_dump_format(fmt_ctx, 0, input_filename, 0); end: avformat_close_input(&fmt_ctx); #if Custom_AVIOContext /* note: the internal buffer could have changed, and be != avio_ctx_buffer */ if (avio_ctx) { av_freep(&avio_ctx->buffer); av_freep(&avio_ctx); } av_file_unmap(buffer, buffer_size); #endif if (ret < 0) { fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); return 1; } return 0; }
2. encode
ffmpeg-4.1/doc/examples/encode_video.c
#!/bin/sh APP = encode_video INCLUDE = \ -I ../bin/include LIB = \ -L ../bin/lib SRC = encode_video.c CFLAGS = -g -O0 -lstdc++ LDFLAGS = -lavformat -lavdevice -lavfilter -lavcodec -lavutil -lswscale -lswresample -lm -pthread -ldl -lz -llzma -lx264 out: gcc $(SRC) -o $(APP) $(LIB) $(INCLUDE) $(CFLAGS) $(LDFLAGS) clean: rm encode_video
./encode_video test.h264 libx264
./encode_video test.mp4 mpeg4
3. decode
3.0 avcodec_send_packet + avcodec_receive_frame
ffmpeg-4.1/doc/examples/decode_video.c (decode h264 video stream from inbuf)
1) find decoder
eg: AV_CODEC_ID_H264
if (argc <= 2) { fprintf(stderr, "Usage: %s <input file> <output file>\n", argv[0]); exit(0); } filename = argv[1]; outfilename = argv[2]; pkt = av_packet_alloc(); if (!pkt) exit(1); /* set end of buffer to 0 (this ensures that no overreading happens for damaged MPEG streams) */ memset(inbuf + INBUF_SIZE, 0, AV_INPUT_BUFFER_PADDING_SIZE); /* find the MPEG-1 video decoder */ //codec = avcodec_find_decoder(AV_CODEC_ID_MPEG1VIDEO); codec = avcodec_find_decoder(AV_CODEC_ID_H264); if (!codec) { fprintf(stderr, "Codec not found\n"); exit(1); }
2) decode
ret = avcodec_send_packet(dec_ctx, pkt);
while (ret >= 0) {
ret = avcodec_receive_frame(dec_ctx, frame);
}
./decode_video test.h264 ttt
3.1 avcodec_decode_video2
decode h264 video stream from file
ffmpeg-4.1/tests/api/api-h264-test.c
ffmpeg-4.1/tests/api/api-band-test.c
4. avio + decode (Custom AVIOContext + H264 decode)
4.1 Custom AVIOContext vs File as Input Source
ffmpeg-4.1/doc/examples/avio_reading.c + ffmpeg-4.1/tests/api/api-h264-test.c(ffmpeg-4.1/tests/api/api-band-test.c)
/* * Copyright (c) 2015 Ludmila Glinskih * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /** * H264 codec test. */ #define Custom_AVIOContext (1) #include "libavutil/adler32.h" #include "libavcodec/avcodec.h" #include "libavformat/avformat.h" #include "libavutil/imgutils.h" #if Custom_AVIOContext #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/file.h> struct buffer_data { uint8_t *ptr; size_t size; ///< size left in the buffer }; static int read_packet(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, bd->size); if (!buf_size) return AVERROR_EOF; //printf("ptr:%p size:%zu\n", bd->ptr, bd->size); //printf("buf_size:%d\n", buf_size); /* copy internal buffer data to buf */ memcpy(buf, bd->ptr, buf_size); bd->ptr += buf_size; bd->size -= buf_size; return buf_size; } #endif static int video_decode_example(const char *input_filename) { AVCodec *codec = NULL; AVCodecContext *ctx= NULL; AVCodecParameters *origin_par = NULL; AVFrame *fr = NULL; uint8_t *byte_buffer = NULL; AVPacket pkt; AVFormatContext *fmt_ctx = NULL; int number_of_written_bytes; int video_stream; int got_frame = 0; int byte_buffer_size; int i = 0; int result; int end_of_stream = 0;\ #if Custom_AVIOContext AVIOContext *avio_ctx = NULL; uint8_t *buffer = NULL, *avio_ctx_buffer = NULL; size_t buffer_size, avio_ctx_buffer_size = 4096; int ret = 0; struct buffer_data bd = { 0 }; /* slurp file content into buffer */ ret = av_file_map(input_filename, &buffer, &buffer_size, 0, NULL); if (ret < 0) return -1; /* fill opaque structure used by the AVIOContext read callback */ bd.ptr = buffer; bd.size = buffer_size; if (!(fmt_ctx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); return -1; } avio_ctx_buffer = av_malloc(avio_ctx_buffer_size); if (!avio_ctx_buffer) { ret = AVERROR(ENOMEM); return -1; } avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, NULL, NULL); if (!avio_ctx) { ret = AVERROR(ENOMEM); return -1; } fmt_ctx->pb = avio_ctx; result = avformat_open_input(&fmt_ctx, NULL, NULL, NULL); #else result = avformat_open_input(&fmt_ctx, input_filename, NULL, NULL); #endif if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Can't open file\n"); return result; } result = avformat_find_stream_info(fmt_ctx, NULL); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Can't get stream info\n"); return result; } video_stream = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0); if (video_stream < 0) { av_log(NULL, AV_LOG_ERROR, "Can't find video stream in input file\n"); return -1; } origin_par = fmt_ctx->streams[video_stream]->codecpar; codec = avcodec_find_decoder(origin_par->codec_id); if (!codec) { av_log(NULL, AV_LOG_ERROR, "Can't find decoder\n"); return -1; } ctx = avcodec_alloc_context3(codec); if (!ctx) { av_log(NULL, AV_LOG_ERROR, "Can't allocate decoder context\n"); return AVERROR(ENOMEM); } result = avcodec_parameters_to_context(ctx, origin_par); if (result) { av_log(NULL, AV_LOG_ERROR, "Can't copy decoder context\n"); return result; } result = avcodec_open2(ctx, codec, NULL); if (result < 0) { av_log(ctx, AV_LOG_ERROR, "Can't open decoder\n"); return result; } fr = av_frame_alloc(); if (!fr) { av_log(NULL, AV_LOG_ERROR, "Can't allocate frame\n"); return AVERROR(ENOMEM); } byte_buffer_size = av_image_get_buffer_size(ctx->pix_fmt, ctx->width, ctx->height, 16); byte_buffer = av_malloc(byte_buffer_size); if (!byte_buffer) { av_log(NULL, AV_LOG_ERROR, "Can't allocate buffer\n"); return AVERROR(ENOMEM); } printf("#tb %d: %d/%d\n", video_stream, fmt_ctx->streams[video_stream]->time_base.num, fmt_ctx->streams[video_stream]->time_base.den); i = 0; av_init_packet(&pkt); do { if (!end_of_stream) if (av_read_frame(fmt_ctx, &pkt) < 0) end_of_stream = 1; if (end_of_stream) { pkt.data = NULL; pkt.size = 0; } if (pkt.stream_index == video_stream || end_of_stream) { got_frame = 0; if (pkt.pts == AV_NOPTS_VALUE) pkt.pts = pkt.dts = i; result = avcodec_decode_video2(ctx, fr, &got_frame, &pkt); if (result < 0) { av_log(NULL, AV_LOG_ERROR, "Error decoding frame\n"); return result; } if (got_frame) { number_of_written_bytes = av_image_copy_to_buffer(byte_buffer, byte_buffer_size, (const uint8_t* const *)fr->data, (const int*) fr->linesize, ctx->pix_fmt, ctx->width, ctx->height, 1); if (number_of_written_bytes < 0) { av_log(NULL, AV_LOG_ERROR, "Can't copy image to buffer\n"); return number_of_written_bytes; } //printf("number_of_written_bytes %d %dx%d\n", number_of_written_bytes, ctx->width, ctx->height); printf("%d, %10"PRId64", %10"PRId64", %8"PRId64", %8d, 0x%08lx\n", video_stream, fr->pts, fr->pkt_dts, fr->pkt_duration, number_of_written_bytes, av_adler32_update(0, (const uint8_t*)byte_buffer, number_of_written_bytes)); } av_packet_unref(&pkt); av_init_packet(&pkt); } i++; } while (!end_of_stream || got_frame); av_packet_unref(&pkt); av_frame_free(&fr); avcodec_close(ctx); avformat_close_input(&fmt_ctx); avcodec_free_context(&ctx); av_freep(&byte_buffer); return 0; } int main(int argc, char **argv) { if (argc < 2) { av_log(NULL, AV_LOG_ERROR, "Incorrect input\n"); return 1; } if (video_decode_example(argv[1]) != 0) return 1; return 0; }
4.2 Custom AVIOContext vs File as Input Source
tutorial01.c
// tutorial01.c // Code based on a tutorial by Martin Bohme (boehme@inb.uni-luebeckREMOVETHIS.de) // Tested on Gentoo, CVS version 5/01/07 compiled with GCC 4.1.1 // With updates from https://github.com/chelyaev/ffmpeg-tutorial // Updates tested on: // LAVC 54.59.100, LAVF 54.29.104, LSWS 2.1.101 // on GCC 4.7.2 in Debian February 2015 // A small sample program that shows how to use libavformat and libavcodec to // read video from a file. // // Use // // gcc -o tutorial01 tutorial01.c -lavformat -lavcodec -lswscale -lz // // to build (assuming libavformat and libavcodec are correctly installed // your system). // // Run using // // tutorial01 myvideofile.mpg // // to write the first five frames from "myvideofile.mpg" to disk in PPM // format. #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libswscale/swscale.h> #include <stdio.h> // compatibility with newer API #if LIBAVCODEC_VERSION_INT < AV_VERSION_INT(55,28,1) #define av_frame_alloc avcodec_alloc_frame #define av_frame_free avcodec_free_frame #endif void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame) { FILE *pFile; char szFilename[32]; int y; // Open file sprintf(szFilename, "frame%d.ppm", iFrame); pFile=fopen(szFilename, "wb"); if(pFile==NULL) return; // Write header fprintf(pFile, "P6\n%d %d\n255\n", width, height); // Write pixel data for(y=0; y<height; y++) fwrite(pFrame->data[0]+y*pFrame->linesize[0], 1, width*3, pFile); // Close file fclose(pFile); } #define Custom_AVIOContext (1) #if Custom_AVIOContext #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> #include <libavformat/avio.h> #include <libavutil/file.h> struct buffer_data { uint8_t *ptr; size_t size; ///< size left in the buffer }; static int read_packet(void *opaque, uint8_t *buf, int buf_size) { struct buffer_data *bd = (struct buffer_data *)opaque; buf_size = FFMIN(buf_size, bd->size); if (!buf_size) return AVERROR_EOF; //printf("ptr:%p size:%zu\n", bd->ptr, bd->size); //printf("buf_size:%d\n", buf_size); /* copy internal buffer data to buf */ memcpy(buf, bd->ptr, buf_size); bd->ptr += buf_size; bd->size -= buf_size; return buf_size; } #endif int main(int argc, char *argv[]) { // Initalizing these to NULL prevents segfaults! AVFormatContext *pFormatCtx = NULL; int i, videoStream; AVCodecContext *pCodecCtxOrig = NULL; AVCodecContext *pCodecCtx = NULL; AVCodec *pCodec = NULL; AVFrame *pFrame = NULL; AVFrame *pFrameRGB = NULL; AVPacket packet; int frameFinished; int numBytes; uint8_t *buffer = NULL; struct SwsContext *sws_ctx = NULL; if(argc < 2) { printf("Please provide a movie file\n"); return -1; } // Register all formats and codecs av_register_all(); #if Custom_AVIOContext AVIOContext *avio_ctx = NULL; uint8_t *avio_ctx_buffer = NULL; size_t buffer_size, avio_ctx_buffer_size = 4096; int ret = 0; struct buffer_data bd = { 0 }; /* slurp file content into buffer */ ret = av_file_map(argv[1], &buffer, &buffer_size, 0, NULL); if (ret < 0) return -1; /* fill opaque structure used by the AVIOContext read callback */ bd.ptr = buffer; bd.size = buffer_size; if (!(pFormatCtx = avformat_alloc_context())) { ret = AVERROR(ENOMEM); return -1; } avio_ctx_buffer = av_malloc(avio_ctx_buffer_size); if (!avio_ctx_buffer) { ret = AVERROR(ENOMEM); return -1; } avio_ctx = avio_alloc_context(avio_ctx_buffer, avio_ctx_buffer_size, 0, &bd, &read_packet, NULL, NULL); if (!avio_ctx) { ret = AVERROR(ENOMEM); return -1; } pFormatCtx->pb = avio_ctx; // Open video file if(avformat_open_input(&pFormatCtx, NULL, NULL, NULL)!=0) return -1; // Couldn't open file #else // Open video file if(avformat_open_input(&pFormatCtx, argv[1], NULL, NULL)!=0) return -1; // Couldn't open file #endif // Retrieve stream information if(avformat_find_stream_info(pFormatCtx, NULL)<0) return -1; // Couldn't find stream information // Dump information about file onto standard error av_dump_format(pFormatCtx, 0, argv[1], 0); // Find the first video stream videoStream=-1; for(i=0; i<pFormatCtx->nb_streams; i++) if(pFormatCtx->streams[i]->codec->codec_type==AVMEDIA_TYPE_VIDEO) { videoStream=i; break; } if(videoStream==-1) return -1; // Didn't find a video stream // Get a pointer to the codec context for the video stream pCodecCtxOrig=pFormatCtx->streams[videoStream]->codec; // Find the decoder for the video stream pCodec=avcodec_find_decoder(pCodecCtxOrig->codec_id); if(pCodec==NULL) { fprintf(stderr, "Unsupported codec!\n"); return -1; // Codec not found } // Copy context pCodecCtx = avcodec_alloc_context3(pCodec); if(avcodec_copy_context(pCodecCtx, pCodecCtxOrig) != 0) { fprintf(stderr, "Couldn't copy codec context"); return -1; // Error copying codec context } // Open codec if(avcodec_open2(pCodecCtx, pCodec, NULL)<0) return -1; // Could not open codec // Allocate video frame pFrame=av_frame_alloc(); // Allocate an AVFrame structure pFrameRGB=av_frame_alloc(); if(pFrameRGB==NULL) return -1; // Determine required buffer size and allocate buffer numBytes=avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); buffer=(uint8_t *)av_malloc(numBytes*sizeof(uint8_t)); // Assign appropriate parts of buffer to image planes in pFrameRGB // Note that pFrameRGB is an AVFrame, but AVFrame is a superset // of AVPicture avpicture_fill((AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_RGB24, pCodecCtx->width, pCodecCtx->height); // initialize SWS context for software scaling sws_ctx = sws_getContext(pCodecCtx->width, pCodecCtx->height, pCodecCtx->pix_fmt, pCodecCtx->width, pCodecCtx->height, AV_PIX_FMT_RGB24, SWS_BILINEAR, NULL, NULL, NULL ); // Read frames and save first five frames to disk i=0; while(av_read_frame(pFormatCtx, &packet)>=0) { // Is this a packet from the video stream? if(packet.stream_index==videoStream) { // Decode video frame avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); // Did we get a video frame? if(frameFinished) { // Convert the image from its native format to RGB sws_scale(sws_ctx, (uint8_t const * const *)pFrame->data, pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data, pFrameRGB->linesize); // Save the frame to disk if(++i<=5) SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i); } } // Free the packet that was allocated by av_read_frame av_free_packet(&packet); } // Free the RGB image av_free(buffer); av_frame_free(&pFrameRGB); // Free the YUV frame av_frame_free(&pFrame); // Close the codecs avcodec_close(pCodecCtx); avcodec_close(pCodecCtxOrig); // Close the video file avformat_close_input(&pFormatCtx); return 0; }
5. mux
5.1 ffmpeg-4.1/doc/examples/muxing.c
This program generates a synthetic aac audio and h264 video frames, encodes and muxes them into a mp4/ps/ts/flv ... and so on container.
fmt = oc->oformat; fmt->video_codec = AV_CODEC_ID_H264; fmt->audio_codec = AV_CODEC_ID_MP2; //ffmpeg-4.2.2 default support //fmt->audio_codec = AV_CODEC_ID_AAC; //ffmpeg-4.1 default support /* Add the audio and video streams using the default format codecs * and initialize the codecs. */ if (fmt->video_codec != AV_CODEC_ID_NONE) { add_stream(&video_st, oc, &video_codec, fmt->video_codec); have_video = 1; encode_video = 1; } if (fmt->audio_codec != AV_CODEC_ID_NONE) { add_stream(&audio_st, oc, &audio_codec, fmt->audio_codec); have_audio = 1; encode_audio = 1; }
dong@ubuntu:~/2019-nCoV/container$ tree
.
├── build.sh
├── muxing
├── muxing.c
├── test.avi
├── test.flv
├── test.mp4
├── test.ps
└── test.ts
0 directories, 8 files
log_packet printf output audio/audio package info.
static void log_packet(const AVFormatContext *fmt_ctx, const AVPacket *pkt);
5.2 mp4 muxer
https://github.com/slmax2017/ffmpeg_mp4_h264_mux
#include <iostream> #include <vector> #include <algorithm> using namespace std; extern "C" { #include "libavcodec/avcodec.h" #include "libavutil/mem.h" #include "libavutil/imgutils.h" #include "libavformat/avformat.h" } int err = -1; char buf[1024] = { 0 }; #define IN_VEDIO_FILE "cuc_ieschool.h264" #define IN_AUDIO_FILE "cuc_ieschool.mp3" #define OUT_FILE "cuc_ieschool.mp4" #define ptr_check(x) \ do {\ if (!x){\ printf("operator fail"); \ return -1; \ }\ }while(0) #define void_handle(x) \ if ((err = (x)) < 0) {\ memset(buf, 0, 1024); \ av_strerror(err, buf, 1024); \ printf("err msg = %s", buf); \ return -1; \ } #define ret_handle(x, r) \ if ((r = (x)) < 0) {\ memset(buf, 0, 1024); \ av_strerror(r, buf, 1024); \ printf("err msg = %s", buf); \ return -1; \ } int main() { AVFormatContext *in_vedio_ctx = NULL, *in_audio_ctx = NULL, *out_ctx = NULL; vector<int> stream_indexs; bool isVedio = true; //h264 info void_handle(avformat_open_input(&in_vedio_ctx, IN_VEDIO_FILE, NULL, NULL)); void_handle(avformat_find_stream_info(in_vedio_ctx, NULL)); //mp3 info void_handle(avformat_open_input(&in_audio_ctx, IN_AUDIO_FILE, NULL, NULL)); void_handle(avformat_find_stream_info(in_audio_ctx, NULL)); //mp4 init void_handle(avformat_alloc_output_context2(&out_ctx, NULL, NULL, OUT_FILE)); //get stream int vedio_stream_index = -1, audio_stream_index = -1; ret_handle(av_find_best_stream(in_vedio_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0), vedio_stream_index); ret_handle(av_find_best_stream(in_audio_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0), audio_stream_index); stream_indexs.push_back(vedio_stream_index); stream_indexs.push_back(audio_stream_index); //为输出上下文创建流 for_each(stream_indexs.begin(), stream_indexs.end(), [&](int index) { AVStream *out_stream = avformat_new_stream(out_ctx, NULL); ptr_check(out_stream); void_handle(avcodec_parameters_from_context(out_stream->codecpar, isVedio ? in_vedio_ctx->streams[index]->codec : in_audio_ctx->streams[index]->codec)); isVedio = false; if (out_ctx->oformat->flags & AVFMT_GLOBALHEADER) { out_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER; } }); //打开输出文件 if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) { void_handle(avio_open(&out_ctx->pb, OUT_FILE, AVIO_FLAG_READ_WRITE)); } //写入文件头 avformat_write_header(out_ctx, NULL); //开始写文件 AVPacket *packet = av_packet_alloc(); int64_t ts_a = 0, ts_b = 0; int64_t *ts_p = NULL; int out_stream_index = -1; AVFormatContext *cur_ctx = NULL; AVStream *cur_stream = NULL; int frame = 0; while (1) { //指定当前读取视频还是音视 if (av_compare_ts(ts_a, in_vedio_ctx->streams[vedio_stream_index]->time_base, ts_b, in_audio_ctx->streams[audio_stream_index]->time_base) <= 0) { cur_ctx = in_vedio_ctx; ts_p = &ts_a; cur_stream = in_vedio_ctx->streams[vedio_stream_index]; out_stream_index = 0; } else { cur_ctx = in_audio_ctx; ts_p = &ts_b; cur_stream = in_audio_ctx->streams[audio_stream_index]; out_stream_index = 1; } if (av_read_frame(cur_ctx, packet) < 0) { memset(buf, 0, 1024); av_strerror(err, buf, 1024); printf(buf); break; } //计算pts dts, 这里只是计算出当前的刻度,后面需要再计算成具体的时间 if (packet->pts == AV_NOPTS_VALUE) { //计算出输入(原始)视频一帧多长时间,结果单位为微妙 int64_t each_frame_time = (double)AV_TIME_BASE / av_q2d(cur_stream->r_frame_rate); //以原始一帧的持续时间除以时间基,则时间刻度就有了,由于时间基的单位为秒.而持续时间(each_frame_time)为微妙,故还需要除以AV_TIME_BASE packet->pts = (double)(frame++ * each_frame_time) / (double)(av_q2d(cur_stream->time_base) * AV_TIME_BASE); packet->dts = packet->pts; //一帧的时间为each_frame_time微妙,除以AV_TIME_BASE就是秒,再除以时间基,则时间刻度就出来了. packet->duration = (double)each_frame_time / (double)(av_q2d(cur_stream->time_base) * AV_TIME_BASE); } *ts_p = packet->pts; //计算pts对应的具体的时间 av_packet_rescale_ts(packet, cur_stream->time_base, out_ctx->streams[out_stream_index]->time_base); packet->stream_index = out_stream_index; printf("write file pts = %lld, index = %d\n", packet->pts, packet->stream_index); //写入文件 void_handle(av_interleaved_write_frame(out_ctx, packet)); av_packet_unref(packet); } //写文件尾 void_handle(av_write_trailer(out_ctx)); if (!(out_ctx->oformat->flags & AVFMT_NOFILE)) { void_handle(avio_close(out_ctx->pb)); } avformat_close_input(&in_audio_ctx); avformat_close_input(&in_vedio_ctx); avformat_free_context(out_ctx); av_packet_free(&packet); return 0; }
g++ main.cpp -o main `pkg-config --cflags --libs libavdevice libavformat libavfilter libavcodec libswresample libswscale libavutil` -lx264 -std=c++11
最简单的基于FFmpeg的封装格式处理系列文章列表:
最简单的基于FFmpeg的封装格式处理:视音频分离器简化版(demuxer-simple)
最简单的基于FFmpeg的封装格式处理:视音频分离器(demuxer)
最简单的基于FFmpeg的封装格式处理:视音频复用器(muxer)
最简单的基于FFMPEG的封装格式处理:封装格式转换(remuxer)
6. Tutorial 05: Synching Video
7. Tutorial 06: Synching Audio
http://dranger.com/ffmpeg/tutorial05.html
http://dranger.com/ffmpeg/tutorial06.html
8. Tutorial 07: Seeking
http://dranger.com/ffmpeg/tutorial07.html
debug
9. avio + mux
Avidemux is a simple platform video editor for Linux, Windows and MacOsX.
https://github.com/mean00/avidemux2
http://www.ffmpeg-archive.org/How-to-mux-a-raw-h264-es-into-some-container-without-x264-installed-td4663139.html
网友整理得很好
https://www.cnblogs.com/leisure_chn/category/1351812.html