FFmpeg獲取來自網絡MP4的音視頻數據

例如手機端URL投屏之類的應用

ffmpeg-3.4編譯出lib庫和頭文件
配置文件可以是這樣 config.sh

#!/bin/bash

export PREFIX=./../ffmpeg

./configure \
    --disable-yasm \
    --disable-ffplay \
    --disable-ffprobe \
    --disable-ffserver \
    --disable-debug \
    --disable-zlib \
    --disable-bzlib \
    --disable-static \
    --disable-stripping \
    --enable-ffmpeg \
    --enable-shared \
    --enable-gpl \
    --enable-small \
    --target-os=linux \
    --arch=arm \
    --enable-cross-compile \
    --cross-prefix=arm-linux- \
    --cc=arm-linux-gcc \
    --prefix=$PREFIX \
    --disable-encoders \
    --enable-decoders \
    --disable-muxers \
    --enable-demuxers \
    --enable-parsers \
    --enable-bsfs \
    --enable-protocols \
    --disable-filters \
    --disable-avfilter \
    --disable-swscale \
    --disable-swresample \
    --disable-devices \
    --disable-postproc \
    --enable-network \
    --enable-indev=v4l2

url.c 供參考

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>

#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libavutil/time.h>

AVBitStreamFilterContext *mH264bsfc = NULL;
AVFormatContext *mAvFmtCtx = NULL;
AVDictionary *mOptions = NULL;
AVPacket mPacket;
int mVIdx = -1;
int mAIdx = -1;
enum AVCodecID mVideoCodec;
enum AVCodecID mAudioCodec;

int urlOpen(const char *url)
{
    // h264有兩種封裝, 一種是annexb模式, 是傳統模式, 有startcode(0x00000001), SPS和PPS是在ES中
    // 另一種是AVCC模式, 一般用mp4、mkv、flv容器封裝, 沒有startcode, SPS和PPS以及其它信息被封裝在container中
    // 很多解碼器只支持annexb這種模式, 因此需要將mp4模式轉換成annexb模式
    // ffmpeg讀取mp4中的H264數據, 並不能直接得到NALU
    // 因此ffmpeg中提供了一個流過濾器"h264_mp4toannexb"可以完成這項工作
    // 流過濾器"h264_mp4toannexb", 在av_register_all()函數中會被註冊
    mH264bsfc = av_bitstream_filter_init("h264_mp4toannexb");
    if (NULL == mH264bsfc) {
        printf("av_bitstream_filter_init failed!\n");
        return -1;
    }

    // register_container_codec
    av_register_all();

    avformat_network_init();

    mAvFmtCtx = avformat_alloc_context();
    mAvFmtCtx->flags |= AVFMT_FLAG_NONBLOCK;

    // open stream
    if (avformat_open_input(&mAvFmtCtx, url, NULL, &mOptions) != 0) {
        printf("avformat_open_input failed! path=%s\n", url);
        return -1;
    }

    // parse stream info
    if (avformat_find_stream_info(mAvFmtCtx, NULL) < 0) {
        printf("avformat_find_stream_info failed!\n");
        return -1;
    }

    // find video stream
    mVIdx = av_find_best_stream(mAvFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (mVIdx < 0) {
        printf("av_find_best_stream failed! (videoIdx %d)\n", mVIdx);
        return -1;
    }

    // find audio stream
    mAIdx = av_find_best_stream(mAvFmtCtx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if (mAIdx < 0) {
        printf("av_find_best_stream failed! (audioIdx %d)\n", mAIdx);
        mAIdx = -1;
    }

    printf("av_find_best_stream (v %d, a %d)\n", mVIdx, mAIdx);
    return 0;
}

// 定位到指定的時間, 處理快進快退的
void setPosSec(double posSec)
{
    AVStream *vStream = mAvFmtCtx->streams[mVIdx];

    int seekPosSec = (int)(posSec+0.5); // 小數部分四捨五入
    int64_t seek = seekPosSec * AV_TIME_BASE;
    printf("%s seekPosSec = %d, seek = %lld\n", __func__, seekPosSec, seek);

    // AVSEEK_FLAG_BACKWARD: 如果seek到的時間點不是關鍵幀, 則這個flag會往回seek到最近的關鍵幀
    // 加上這個flag可以保證關鍵幀, 但是又沒法保證seek的精度, 因爲有可能會往回seek
    // 定位到非關鍵幀肯定是不行的, 因爲視頻的解碼需要依賴於關鍵幀
    // 如果跳過關鍵幀, 解碼出來的圖像是會出現馬賽克的, 影響用戶體驗
    av_seek_frame(mAvFmtCtx, -1, seek, AVSEEK_FLAG_BACKWARD);
}

int getVideoInfo(void)
{
    if (NULL == mAvFmtCtx) {
        printf("%s failed (mAvFmtCtx is null)\n", __func__);
        return -1;
    }
    if (-1 == mVIdx) {
        printf("%s failed (no video stream)\n", __func__);
        return -1;
    }

    AVStream *vStream = mAvFmtCtx->streams[mVIdx];
    AVCodecParameters *vCodecPar = vStream->codecpar;
    if (NULL == vCodecPar) {
        printf("%s failed (vCodecPar is null)\n", __func__);
        return -1;
    }

    mVideoCodec = vCodecPar->codec_id;
    if (AV_CODEC_ID_MJPEG == mVideoCodec) {
        printf("%s video codec is MJPEG\n", __func__);
    } else if (AV_CODEC_ID_H264 == mVideoCodec) {
        printf("%s video codec is H264\n", __func__);
    } else if (AV_CODEC_ID_HEVC == mVideoCodec) {
        printf("%s video codec is H265\n", __func__);
    } else {
        printf("%s video codec is 0x%x\n", __func__, mVideoCodec);
    }

    AVRational avgFps = vStream->avg_frame_rate;
    printf("%s video info is (w %d, h %d, fps %lf)\n", __func__, vCodecPar->width, vCodecPar->height, av_q2d(avgFps));

    printf("%s video duration = %lld, time_base.num = %d, time_base.den = %d, total time = %.2fs\n",
        __func__, vStream->duration, vStream->time_base.num, vStream->time_base.den, vStream->duration * av_q2d(vStream->time_base));

    return 0;
}

int getAudioInfo(void)
{
    if (NULL == mAvFmtCtx) {
        printf("%s failed (mAvFmtCtx is null)\n", __func__);
        return -1;
    }
    if (mAIdx < 0) {
        printf("%s failed (no audio stream)\n", __func__);
        return -1;
    }

    AVStream *aStream = mAvFmtCtx->streams[mAIdx];
    AVCodecParameters *aCodecPar = aStream->codecpar;
    if (NULL == aCodecPar) {
        printf("%s failed (aCodecPar is null)\n", __func__);
        return -1;
    }

    mAudioCodec = aCodecPar->codec_id;
    if ((mAudioCodec >= AV_CODEC_ID_FIRST_AUDIO) && (mAudioCodec <= AV_CODEC_ID_PCM_F24LE)) {
        printf("%s audio codec is PCM\n", __func__);
    } else if (AV_CODEC_ID_MP3 == mAudioCodec) {
        printf("%s audio codec is MP3\n", __func__);
    } else if (AV_CODEC_ID_AAC == mAudioCodec) {
        printf("%s audio codec is AAC\n", __func__);
    } else {
        printf("%s audio codec is 0x%x\n", __func__, mAudioCodec);
    }

    printf("%s audio sample_rate %d\n", __func__, aCodecPar->sample_rate);

    return 0;
}

int saveFile(char *buf, int len, int port)
{
    FILE *f = NULL;
    char *filename = NULL;

    if(port == 0) {
        filename = "/nfsroot/datagram.video";
    } else {
        filename = "/nfsroot/datagram.audio";
    }

    f = fopen(filename, "ab");
    if (f == NULL) {
        printf("open file(%s) failed!\n", filename);
        return -1;
    }

    fwrite(buf, 1, len, f);
    fclose(f);

    return 0;
}

void *threadLoop(void *data)
{
    setPosSec(0.0);
    
    while(1) {
        if (av_read_frame(mAvFmtCtx, &mPacket) >= 0) {
            // video case
            if (-1 != mVIdx && mPacket.stream_index == mVIdx) {
                // ffmpeg讀到的mPacket.data中沒有0x00000001的分隔符
                // 需要 av_bitstream_filter_filter 來轉換一下
                av_bitstream_filter_filter(mH264bsfc, mAvFmtCtx->streams[mVIdx]->codec, NULL, &mPacket.data, &mPacket.size, mPacket.data, mPacket.size, 0);

                printf("video mPacket.pts = %lld, mPacket.size = %d\n", mPacket.pts, mPacket.size);
                saveFile((char*)mPacket.data, mPacket.size, 0);
            }

            // audio case
            if (-1 != mAIdx && mPacket.stream_index == mAIdx) {
                printf("audio mPacket.size = %d\n", mPacket.size);
                saveFile((char*)mPacket.data, mPacket.size, 1);
            }
        } else {
            av_usleep(10000); // 10ms
        }

        // av_read_frame would allocate memory on mPacket.data
        av_packet_unref(&mPacket);
    }
}

int main(int argc, char *argv[])
{
    pthread_t mThread;

    if(urlOpen("http://192.168.199.160:7001/1/b9ac0c87-bd52-50fe-ba6a-3ca5873b48cd.mp4") < 0) {
        return -1;
    }

    getVideoInfo();
    getAudioInfo();

    if (pthread_create(&mThread, NULL, threadLoop, NULL) != 0) {
        printf("createThread thread failed!\n");
        return -1;
    }

    sleep(1);

    void *joinRes;
    pthread_join(mThread, &joinRes);

    // release resource
    avformat_close_input(&mAvFmtCtx);
    mAvFmtCtx = NULL;

    av_bitstream_filter_close(mH264bsfc);
    mH264bsfc = NULL;

    av_dict_free(&mOptions);
    mOptions = NULL;

    return 0;
}

編譯
arm-linux-gcc url.c -I/ffmpeg/include -L/ffmpeg/lib -lpthread -lavdevice -lavformat -lavcodec -lavutil

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章