ffmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。同时 ffmpeg 也提供C API,但是 ffmpeg 的 API 文档缺失,找遍了Google、百度都没找到,只找到几个非官方写的 demo。这篇文章主要整合几个示例供大家参考。

ffmpeg 库的接口都是c函数,其头文件也没有 extern "C" 的声明,所以在 cpp 文件里调用 ffmpeg 函数要注意了。
一般来说,一个用 C 写成的库如果想被 C/C++ 同时可以使用,那在头文件应该加上:

#ifdef __cplusplus
extern "C" {
#endif

#ifdef __cplusplus
} // endof extern "C"
#endif

如果文件名是 main.c,里面调用 ffmpeg 的接口没有问题;但换成 main.cpp 后,就会报错 undefined reference。解决方法:

extern "C"
{
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}

获取视频信息

下面一段代码演示如何获取一个视频文件的详细信息:

#include "stdio.h"
#include <iostream>
 
extern "C"
{
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
}
 
void printAudioFrameInfo(const AVCodecContext* codecContext, const AVFrame* frame)
{
    // See the following to know what data type (unsigned char, short, float, etc) to use to access the audio data:
    // http://ffmpeg.org/doxygen/trunk/samplefmt_8h.html#af9a51ca15301871723577c730b5865c5
    std::cout << "Audio frame info:\n"
              << "  Sample count: " << frame->nb_samples << '\n'
              << "  Channel count: " << codecContext->channels << '\n'
              << "  Format: " << av_get_sample_fmt_name(codecContext->sample_fmt) << '\n'
              << "  Bytes per sample: " << av_get_bytes_per_sample(codecContext->sample_fmt) << '\n'
              << "  Is planar? " << av_sample_fmt_is_planar(codecContext->sample_fmt) << '\n';
 
 
    std::cout << "frame->linesize[0] tells you the size (in bytes) of each plane\n";
 
    if (codecContext->channels > AV_NUM_DATA_POINTERS && av_sample_fmt_is_planar(codecContext->sample_fmt))
    {
        std::cout << "The audio stream (and its frames) have too many channels to fit in\n"
                  << "frame->data. Therefore, to access the audio data, you need to use\n"
                  << "frame->extended_data to access the audio data. It's planar, so\n"
                  << "each channel is in a different element. That is:\n"
                  << "  frame->extended_data[0] has the data for channel 1\n"
                  << "  frame->extended_data[1] has the data for channel 2\n"
                  << "  etc.\n";
    }
    else
    {
        std::cout << "Either the audio data is not planar, or there is enough room in\n"
                  << "frame->data to store all the channels, so you can either use\n"
                  << "frame->data or frame->extended_data to access the audio data (they\n"
                  << "should just point to the same data).\n";
    }
 
    std::cout << "If the frame is planar, each channel is in a different element.\n"
              << "That is:\n"
              << "  frame->data[0]/frame->extended_data[0] has the data for channel 1\n"
              << "  frame->data[1]/frame->extended_data[1] has the data for channel 2\n"
              << "  etc.\n";
 
    std::cout << "If the frame is packed (not planar), then all the data is in\n"
              << "frame->data[0]/frame->extended_data[0] (kind of like how some\n"
              << "image formats have RGB pixels packed together, rather than storing\n"
              << " the red, green, and blue channels separately in different arrays.\n";
}
 
int main()
{
    // Initialize FFmpeg
    av_register_all();
 
 
    AVFrame* frame = av_frame_alloc();
    if (!frame)
    {
        std::cout << "Error allocating the frame" << std::endl;
        return 1;
    }
 
    // you can change the file name "01 Push Me to the Floor.wav" to whatever the file is you're reading, like "myFile.ogg" or
    // "someFile.webm" and this should still work
    AVFormatContext* formatContext = NULL;
    if (avformat_open_input(&formatContext, "/home/cjx/2.mp4", NULL, NULL) != 0)
    {
        av_free(frame);
        std::cout << "Error opening the file" << std::endl;
        return 1;
    }
 
    if (avformat_find_stream_info(formatContext, NULL) < 0)
    {
        av_free(frame);
        avformat_close_input(&formatContext);
        std::cout << "Error finding the stream info" << std::endl;
        return 1;
    }
 
    // Find the audio stream
    AVCodec* cdc = nullptr;
    int streamIndex = av_find_best_stream(formatContext, AVMEDIA_TYPE_AUDIO, -1, -1, &cdc, 0);
    if (streamIndex < 0)
    {
        av_free(frame);
        avformat_close_input(&formatContext);
        std::cout << "Could not find any audio stream in the file" << std::endl;
        return 1;
    }
 
    AVStream* audioStream = formatContext->streams[streamIndex];
    AVCodecContext* codecContext = audioStream->codec;
    codecContext->codec = cdc;
 
    if (avcodec_open2(codecContext, codecContext->codec, NULL) != 0)
    {
        av_free(frame);
        avformat_close_input(&formatContext);
        std::cout << "Couldn't open the context with the decoder" << std::endl;
        return 1;
    }
 
    std::cout << "This stream has " << codecContext->channels << " channels and a sample rate of " << codecContext->sample_rate << "Hz" << std::endl;
    std::cout << "The data is in the format " << av_get_sample_fmt_name(codecContext->sample_fmt) << std::endl;
 
    AVPacket readingPacket;
    av_init_packet(&readingPacket);
 
    // Read the packets in a loop
    while (av_read_frame(formatContext, &readingPacket) == 0)
    {
        if (readingPacket.stream_index == audioStream->index)
        {
            AVPacket decodingPacket = readingPacket;
 
            // Audio packets can have multiple audio frames in a single packet
            while (decodingPacket.size > 0)
            {
                // Try to decode the packet into a frame
                // Some frames rely on multiple packets, so we have to make sure the frame is finished before
                // we can use it
                int gotFrame = 0;
                int result = avcodec_decode_audio4(codecContext, frame, &gotFrame, &decodingPacket);
 
                if (result >= 0 && gotFrame)
                {
                    decodingPacket.size -= result;
                    decodingPacket.data += result;
 
                    // We now have a fully decoded audio frame
                    printAudioFrameInfo(codecContext, frame);
                }
                else
                {
                    decodingPacket.size = 0;
                    decodingPacket.data = nullptr;
                }
            }
        }
 
        // You *must* call av_free_packet() after each call to av_read_frame() or else you'll leak memory
        av_free_packet(&readingPacket);
    }
 
    // Some codecs will cause frames to be buffered up in the decoding process. If the CODEC_CAP_DELAY flag
    // is set, there can be buffered up frames that need to be flushed, so we'll do that
    if (codecContext->codec->capabilities & CODEC_CAP_DELAY)
    {
        av_init_packet(&readingPacket);
        // Decode all the remaining frames in the buffer, until the end is reached
        int gotFrame = 0;
        while (avcodec_decode_audio4(codecContext, frame, &gotFrame, &readingPacket) >= 0 && gotFrame)
        {
            // We now have a fully decoded audio frame
            printAudioFrameInfo(codecContext, frame);
        }
    }
 
    // Clean up!
    av_free(frame);
    avcodec_close(codecContext);
    avformat_close_input(&formatContext);
}

视频帧转图片

下面一段代码演示如何将视频的某一帧转换为ppm格式的图片:

extern "C"
{
#include <libavutil/avutil.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswscale/swscale.h>
#include <libavutil/pixfmt.h>
}
 
 
static void SaveFrame(AVFrame *pFrame, int width, int height, int iFrame)
{
    FILE *pFile;
    char szFilename[32];
    int y;
 
    sprintf(szFilename, "/home/cjx/frame%d.ppm", iFrame);
    pFile = fopen(szFilename, "wb");
    if( !pFile )
        return;
    fprintf(pFile, "P6\n%d %d\n255\n", width, height);
 
    for( y = 0; y < height; y++ )
        fwrite(pFrame->data[0] + y * pFrame->linesize[0], 1, width * 3, pFile);
 
    fclose(pFile);
}
 
int main(int argc, const char *argv[])
{
    AVFormatContext *pFormatCtx = NULL;
    int             i, videoStream;
    AVCodecContext  *pCodecCtx;
    AVCodec         *pCodec;
    AVFrame         *pFrame;
    AVFrame         *pFrameRGB;
    AVPacket        packet;
    int             frameFinished;
    int             numBytes;
    uint8_t         *buffer;
 
    av_register_all();
 
    // 先判断文件是否可读写
    if( avformat_open_input(&pFormatCtx, "/home/cjx/2.mp4", NULL, NULL) != 0 )
        return -1;
 
    // 查找文件相关信息
    if( avformat_find_stream_info(pFormatCtx, NULL ) < 0 )
        return -1;
 
    av_dump_format(pFormatCtx, -1, "/home/cjx/2.mp4", 0);
 
    // 找到视频帧的位置
    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;
 
    // 找到对应流的解码器上下文
    pCodecCtx = pFormatCtx->streams[videoStream]->codec;
 
    // 找到对应的解码器
    pCodec = avcodec_find_decoder(pCodecCtx->codec_id);
    // 是否支持该解码
    if( pCodec == NULL )
        return -1;
 
    if( avcodec_open2(pCodecCtx, pCodec, NULL) < 0 )
        return -1;
 
    pFrame = av_frame_alloc();
    if( pFrame == NULL )
        return -1;
 
    pFrameRGB = av_frame_alloc();
    if( pFrameRGB == NULL )
        return -1;
 
    numBytes = avpicture_get_size(AV_PIX_FMT_RGB24, pCodecCtx->width,
                                  pCodecCtx->height);
 
    buffer =(uint8_t*) av_malloc(numBytes);
 
    avpicture_fill( (AVPicture *)pFrameRGB, buffer, AV_PIX_FMT_RGB24,
                    pCodecCtx->width, pCodecCtx->height);
 
    i = 0;
    while( av_read_frame(pFormatCtx, &packet) >= 0 )
    {
        // 如果是视频帧
        if( packet.stream_index == videoStream )
        {
            // frameFinished 非0 就说明可以解码 pFrame 就是一帧
            avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
 
            if( frameFinished ) {
                struct SwsContext *img_convert_ctx = NULL;
                img_convert_ctx =
                        sws_getCachedContext(img_convert_ctx, pCodecCtx->width,
                                             pCodecCtx->height, pCodecCtx->pix_fmt,
                                             pCodecCtx->width, pCodecCtx->height,
                                             AV_PIX_FMT_RGB24, SWS_BICUBIC,
                                             NULL, NULL, NULL);
                if( !img_convert_ctx ) {
                    fprintf(stderr, "Cannot initialize sws conversion context\n");
                    exit(1);
                }
                sws_scale(img_convert_ctx, (const uint8_t* const*)pFrame->data,
                          pFrame->linesize, 0, pCodecCtx->height, pFrameRGB->data,
                          pFrameRGB->linesize);
                if( i++ < 50 )
                    SaveFrame(pFrameRGB, pCodecCtx->width, pCodecCtx->height, i);
            }
        }
        av_free_packet(&packet);
    }
 
    av_free(buffer);
    av_free(pFrameRGB);
    av_free(pFrame);
    avcodec_close(pCodecCtx);
    avformat_close_input(&pFormatCtx);
 
    return 0;
}

遗憾的是找了好久只找到文件转图片的例子,没找到视频流转图片的API和例子。

分享几篇ffmpeg的文章:

参考文章:

标签: ffmpeg, 图片, 视频

添加新评论