例如手機端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