Files
Obsidian-Main/02. PARA/03. Resources(資源)/FFMpeg.md
2022-06-02 17:55:14 +08:00

10 KiB
Raw Blame History

Initialization

av_register_all();
avformat_network_init();

Command line

利用FFMpeg來轉檔

ffmpeg -i <SOURCE.mp4> -t 10 -s <W>x<H> -pix_fmt <OUTPUT_FORMAT> <OUTPUT_FILENAME>
  • -i:來源檔名
  • -t:影片長度(秒)
  • -s影片大小長x寬
  • -pix_fmt:輸出的影像格式

example:

ffmpeg -i test.mp4 -t 10 -s 240x128 -pix_fmt yuv420p out240x128.yuv

Open file

使用avformat_open_input()來打開檔案。

AVFormatContext* pFormatContext = NULL;
char* path = "E:\\download\\Incredible Patagonia_1920x1080.mp4";
AVDictionary* pDict = NULL;

int result = avformat_open_input(&pFormatContext, path, NULL, &pDict);

若是開啟失敗result會是一個非0的值。可以用av_strerror()來印出詳細的錯誤訊息。例:

char errorMsg[1024] = { 0 };
av_strerror(result, errorMsg, sizeof(errorMsg) - 1);
printf("Open file %s error, code = %d, message = %s\n", path, result, errorMsg);

Get file information

avformat_find_stream_info()來把檔案的video/audio相關訊息放到AVFormatContext裡面。例:

result = avformat_find_stream_info(pFormatContext, NULL);

也可以用

av_dump_format(pFormatContext, 0, path, 0);

來直接印出相關訊息不過這方法是Ffmpeg內定的如果要印出自己想要的內容就必須將AVFormatContext裡面的AVStream一一撈出來:

for (int i = 0; i < pFormatContext->nb_streams; i++) {
    AVStream* avStream = pFormatContext->streams[i];

    if (avStream->codecpar->codec_type == AVMEDIA_TYPE_AUDIO) {
        printf("Stream[%d] is a audio stream.\n", i);
        printf("    Sample rate: %d\n", avStream->codecpar->sample_rate);
        printf("    Format     : %d\n", avStream->codecpar->format);
        printf("    Channels   : %d\n", avStream->codecpar->channels);
        printf("    FPS        : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
        printf("    Frame Size : %d\n", avStream->codecpar->frame_size);
        printf("    Codec ID   : %d\n", avStream->codecpar->codec_id);
    } else if (avStream->codecpar->codec_type == AVMEDIA_TYPE_VIDEO) {
        printf("Stream[%d] is a video stream.\n", i);
        printf("    Width : %d\n", avStream->codecpar->width);
        printf("    Height: %d\n", avStream->codecpar->height);
        printf("    FPS   : %d/%d\n", avStream->avg_frame_rate.num, avStream->avg_frame_rate.den);
        printf("    Codec ID: %d\n", avStream->codecpar->codec_id);
    }
}

這個for-loop可以找出檔案裡面的video stream index與audio stream index。

另一個找出video stream index與audio stream index的方法

int videoStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
int audioStream = av_find_best_stream(pFormatContext, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
printf("Found best video stream index: %d\n", videoStream);
printf("Found best audio stream index: %d\n", audioStream);

Demux

重要結構

AVPackt

重要的member
  • AVBufferRef *bufbuffer的reference count基本上不會用到。
  • int64_t pts:顯示順序
  • int64_t dtsdecode順序
  • uint8_t *data實際存data的buffer
  • int size上面那個buffer的大小
操作function
  • AVPacket* av_packet_alloc()產生一個兂的AVPacket
  • AVPacket* av_packet_clone(const AVPacket*)複製一個AVPacket
  • int av_packet_ref(AVPacket*, const AVPacket*)增加reference count
  • av_packet_unref(AVPacket*)減少reference count
  • void av_packet_free(AVPacket**)釋放AVPacket
  • void av_init_packet(AVPacket*)初始化AVPacket
  • int av_packet_from_data(AVPacket*, uint8_t*, int size)從自己的data
  • av_seek_frame
    • AVFormatContext*
    • int stream_index要seek的stream
    • int64_t timestamptimestamp
    • int flagseek的方法
      • #define AVSEEK_FLAG_BACKWARD 1:往後找
      • #define AVSEEK_FLAG_BYTE 2以byte為單位跳到timestamp相對應的byte
      • #define AVSEEK_FLAG_ANY 4跳到任意frame不一定要key frame
      • #define AVSEEK_FLAG_FRAME 8依照frame number來找

Decode

重要結構

AVCodecContext

重要的member
  • int thread_count:執行續的數量
  • AVRational time_base
操作function
  • avcodec_register_all()註冊所有的decoder。
  • AVCodec* avcodec_find_decoder(enum AVCodecID)用codec ID來找到相對應的codec instance
  • AVCodec* avcodec_find_decoder_by_name(const char* name)用codec的名子來找到相對應的codec instance
  • AVCodecContext* avcodec_alloc_context3(const AVCodec*)用codec來建立AVCodecContextcodec只是用來解碼解碼之後的相關資料要放在AVCodecContext裡面。
  • void avcodec_free_context(AVCodecContext**):釋放AVCodecContext
  • int avcodec_open2(AVCodecContext*, const AVCodec*, AVDictionary*)
    • 開始解碼
    • AVDictionary的選項請看libavcodec/options_table.h

AVFrame

重要的member
  • uint8_t* data[AV_NUM_DATA_POINTERS]存放這個frame的資料
  • int linesize[AV_NUM_DATA_POINTERS]存放指向各個plane的pointer
    • Plane mode
      • For video
        • 0Y
        • 1U
        • 2V
        • 3NULL
      • For audio
        • 0Left channel
        • 1Right channel
        • 2NULL
    • Non plane mode
      • For video
        • 0RGB RGB RGB
        • 1NULL
  • int widthFor video, frame width
  • int heightFor video, frame height
  • int nb_samplesFor audio
  • int64_t pts這個frame的pts
  • int64_t pkt_dtsPacket的dts
  • int sample_rateFor audiosampling rate
  • uint64_t channel_layoutFor audio
  • int channelsFor audio
  • int formatFor video it's AVPixelFormat, for audio it's AVSampleFormat
操作function
  • AVFrame* av_frame_alloc()
  • void av_frame_free(AVFrame*)
  • AVFrame* av_frame_clone(const AVFrame*)
  • int av_frame_ref(AVFrame* dst, const AVFrame* src)
  • void av_frame_unref(AVFrame*)
  • int avcodec_send_packet(AVCodecContext*, const AVPacket*)
  • int avcodec_receive_frame(AVCodecContext*, AVFrame*)

Frame scaling

操作function
  • sws_getContext
  • sws_getCachedContext
    SwsContext* sws_getCachedContext(
        SwsContext*, 
        int srcW, int srcH, enum AVPixelFormat srcFormat,
        int dstW, int dstH, enum AVPixelFormat dstFormat,
        int flags,
        SwsFilter srcFilter,
        SwsFilter dstFilter,
        const double* param)
    
  • sws_scale
    int sws_scale(
        SwsContext*,
        const uint8_t* const srcSlice[], // 來源buffer的pointer, 如果是plane mode要丟srcSlice[3]
        const int srcStride[],           // linesize
        int srcSliceY,
        int srcSliceH,                   // image height
        uint8_t* const dst[],            // 目標buffer的pointer, 如果是plane mode要丟dst[3]
        const int dstStride[])           // linesize
    
  • void sws_freeContext(SwsContext*)

Audio resampling

  • SwrContext* swr_alloc()
  • swr_alloc_set_opts
    SwrContext* swr_alloc_set_opts(
        SwrContext*,
        int64_t out_ch_layout,           // Output
        AVSampleFormat outSampleFormat,  // Output
        int outSampleRate,               // Output
        int64_t in_ch_layout,            // Input
        AVSampleFormat inSampleFormat,   // Input
        int inSampleRate,                // Input
        int log_offset,                  // 一般丟0
        void* log_ctx)                   // 一般丟0
    
  • int swr_init(SwrContext*)
  • void swr_free(SwrContext**)
  • swr_convert
    int swt_convert(
        SwrContext*,
        uint8_t** outData,
        int outCount,        // Sample count for one channel
        uint8_t** inData,
        int inCount)         // Sample count for one channel
    

Audio 播放

會用到的class

QAudioFormat

  • setSampleRate
  • setSampleSize
  • setChannelCount
  • setCodec("audio/pcm")
  • setByteOrder(QAudioFormat::LittleEndian)
  • setSampleType(QAudioFormat::UnSignedInt)

QAudioOutput

  • Constuctor參數就是一個QAudioFormat
  • QIODevice* start()
  • suspend()
  • resume()
  • bufferSize()
  • byteFree()
  • periodSize()

QIODevice

  • qint64 write(const char* data, qint64 len)

QBuffer

QBuffer buffer;

if (buffer.open(QIODevice::ReadWrite)) {
    qInfo() << "Buffer Opened!";
    QByteArray data("Hello World");

    for (int i = 0; i < 5; i++) {
        buffer.write(data);
        buffer.write("\r\n");
    }
    //File and device access you may need to flush the data to the device
    //buffer.flush()

    //Move to the first position
    buffer.seek(0);

    qInfo() << buffer.readAll();     // retuen a QVariant
    qInfo() << "closing the buffer";

    //ALWAYS close your device!
    buffer.close();
}

Example

QAudioFormat audioFormat;
audioFormat.setSampleRate(44100);
audioFormat.setSampleSize(16);
audioFormat.setChannelCount(2);
audioFormat.setCodec("audio/pcm");
audioFormat.setByteOrder(QAudioFormat::LittleEndian);
audioFormat.setSampleType(QAudioFormat::UnSignedInt);

QAudioOutput* audioOutput = new QAudioOutput(audioFormat);
QIODevice* ioDevice = audioOutput->start();

int size = audioOutput->periodSize();
char* buffer = new char[size];
FILE *pFile = fopen("E:\\download\\out.pcm", "rb");
while (!feof(pFile)) {
    if (audioOutput->bytesFree() < size) {
        QThread::msleep(1);
    }

    qint64 len = fread(buffer, 1, size, pFile);
    if (len <= 0) {
        break;
    }
    ioDevice->write(buffer, len);
}
fclose(pFile);
delete buffer;
buffer = NULL;

使用FFmpeg decode audio

參考資料