例如录制手机端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 \
--enable-encoders \
--enable-decoders \
--enable-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
ffrecord.cpp 供参考
#include
#include
#include
#include
#include
#include
#include
extern "C" {
#include
#include
#include
}
class FFRecordMP4 {
public:
FFRecordMP4()
:
mStarting(false),
mInputAvFmtCtx(NULL),
mOutputAvFmtCtx(NULL),
mH264bsfc(NULL),
mAACbsfc(NULL),
mVIdx(-1),
mAIdx(-1),
mWidth(0),
mHeight(0),
mFps(0),
mSampleRate(0),
mVideoFramesNum(0),
mInputUrl(""),
mOutputFile("") {}
~FFRecordMP4();
bool openInputStream(const char *url);
void closeInputStream(void);
bool openOutputStream(const char *file);
void closeOutputStream(void);
bool getInputVideoInfo(void);
bool getInputAudioInfo(void);
void setInputUrl(const char *url) { mInputUrl = url; }
void setOutputFile(const char *file) { mOutputFile = file; }
void setStarted(bool status) { mStarting = status; }
std::string getInputUrl(void) { return mInputUrl; }
std::string getOutputFile(void) { return mOutputFile; }
bool getStarted(void) { return mStarting; }
static void *threadRun(void *data);
void threadLoop(void);
private:
bool mStarting;
AVFormatContext *mInputAvFmtCtx;
AVFormatContext *mOutputAvFmtCtx;
AVBitStreamFilterContext *mH264bsfc;
AVBitStreamFilterContext *mAACbsfc;
AVPacket mPacket;
int mVIdx;
int mAIdx;
int mWidth;
int mHeight;
double mFps;
int mSampleRate;
int mVideoFramesNum;
std::string mInputUrl;
std::string mOutputFile;
};
FFRecordMP4::~FFRecordMP4() {
closeInputStream();
closeOutputStream();
mInputUrl = "";
mOutputFile = "";
}
bool FFRecordMP4::openInputStream(const char *url) {
if(url == NULL) {
printf("input stream url == NULL!\n");
return false;
}
if(mInputAvFmtCtx) {
printf("already open input stream!\n");
return false;
}
mAACbsfc = av_bitstream_filter_init("aac_adtstoasc");
if (NULL == mAACbsfc) {
printf("can not create aac_adtstoasc filter!\n");
return false;
}
// 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("can not create h264_mp4toannexb filter!\n");
return false;
}
// register_container_codec, register all codecs, demux and protocols
avcodec_register_all();
av_register_all();
avformat_network_init();
mInputAvFmtCtx = avformat_alloc_context();
mInputAvFmtCtx->flags |= AVFMT_FLAG_NONBLOCK;
// open stream
if (avformat_open_input(&mInputAvFmtCtx, url, NULL, NULL) != 0) {
printf("avformat_open_input failed! [path]=[%s]\n", url);
return false;
}
// parse stream info
if (avformat_find_stream_info(mInputAvFmtCtx, NULL) < 0) {
printf("avformat_find_stream_info failed!\n");
return false;
}
// find video stream
mVIdx = av_find_best_stream(mInputAvFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
if (mVIdx < 0) {
printf("av_find_best_stream failed! (videoIdx %d)\n", mVIdx);
return false;
}
// find audio stream
mAIdx = av_find_best_stream(mInputAvFmtCtx, 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);
av_dump_format(mInputAvFmtCtx, 0, url, 0);
for (int i = 0; i < mInputAvFmtCtx->nb_streams; i++)
{
AVStream *in_stream = mInputAvFmtCtx->streams[i];
printf("codec id: %d, URL: %s\n", in_stream->codec->codec_id, url);
if (in_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
{
mVIdx = i;
mWidth = in_stream->codec->width;
mHeight = in_stream->codec->height;
if((in_stream->avg_frame_rate.den != 0) && (in_stream->avg_frame_rate.num != 0))
{
AVRational avgFps = in_stream->avg_frame_rate;
mFps = av_q2d(avgFps);
}
printf("video stream index: %d, width: %d, height: %d, FrameRate: %.2f\n", mVIdx, mWidth, mHeight, mFps);
}
else if (in_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
{
mAIdx = i;
printf("audio stream index: %d\n", mAIdx);
}
}
mVideoFramesNum = 0;
av_init_packet(&mPacket);
mInputUrl = url;
return true;
}
void FFRecordMP4::closeInputStream(void) {
if (NULL != mInputAvFmtCtx) {
// release resource
avformat_close_input(&mInputAvFmtCtx);
mInputAvFmtCtx = NULL;
}
if (NULL != mH264bsfc) {
av_bitstream_filter_close(mH264bsfc);
mH264bsfc = NULL;
}
if (NULL != mAACbsfc) {
av_bitstream_filter_close(mAACbsfc);
mAACbsfc = NULL;
}
}
// 文件名必须是以.MP4为后缀, 程序会将收到的视频(H264)和音频(AAC)封装到目标文件容器(MP4)里面
bool FFRecordMP4::openOutputStream(const char *file) {
int ret = 0;
char tmpBuf[AV_ERROR_MAX_STRING_SIZE] = {0};
if(file == NULL) {
printf("output stream file == NULL!\n");
return false;
}
if(mOutputAvFmtCtx) {
printf("already open output stream!\n");
return false;
}
// 为输出视频分配媒体文件句柄
avformat_alloc_output_context2(&mOutputAvFmtCtx, NULL, "mp4", file);
if(mOutputAvFmtCtx == NULL) {
printf("can not alloc output context\n");
return false;
}
AVOutputFormat *outputFmt = mOutputAvFmtCtx->oformat;
for (int i = 0; i < mInputAvFmtCtx->nb_streams; i++)
{
AVStream *in_stream = mInputAvFmtCtx->streams[i];
AVStream *out_stream = avformat_new_stream(mOutputAvFmtCtx, in_stream->codec->codec);
if (out_stream == NULL)
{
printf("can not new output stream\n");
return false;
}
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
if (ret < 0)
{
memset(tmpBuf, 0, sizeof(tmpBuf));
printf("can not copy context, url:%s, error msg:%s\n", mInputUrl.c_str(), av_make_error_string(tmpBuf, sizeof(tmpBuf), ret));
return false;
}
if (outputFmt->flags & AVFMT_GLOBALHEADER)
{
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
}
}
av_dump_format(mOutputAvFmtCtx, 0, file, 1);
if (!(outputFmt->flags & AVFMT_NOFILE))
{
ret = avio_open(&mOutputAvFmtCtx->pb, file, AVIO_FLAG_WRITE);
if (ret < 0)
{
memset(tmpBuf, 0, sizeof(tmpBuf));
printf("can not open output io, file:%s, error msg:%s\n", file, av_make_error_string(tmpBuf, sizeof(tmpBuf), ret));
return false;
}
}
// 为媒体文件句柄写入头信息
ret = avformat_write_header(mOutputAvFmtCtx, NULL);
if (ret < 0)
{
memset(tmpBuf, 0, sizeof(tmpBuf));
printf("can not write outputstream header, file:%s, error msg:%s\n", file, av_make_error_string(tmpBuf, sizeof(tmpBuf), ret));
return false;
}
mOutputFile = file;
return true;
}
void FFRecordMP4::closeOutputStream(void) {
if (mOutputAvFmtCtx)
{
// 写入文件尾
av_write_trailer(mOutputAvFmtCtx);
if (!(mOutputAvFmtCtx->oformat->flags & AVFMT_NOFILE))
{
if(mOutputAvFmtCtx->pb)
{
avio_close(mOutputAvFmtCtx->pb);
}
}
avformat_free_context(mOutputAvFmtCtx);
mOutputAvFmtCtx = NULL;
}
}
bool FFRecordMP4::getInputVideoInfo(void) {
if (NULL == mInputAvFmtCtx) {
printf("%s failed (mAvFmtCtx is null)\n", __func__);
return false;
}
if (mVIdx < 0) {
printf("%s failed (no video stream)\n", __func__);
return false;
}
AVStream *vStream = mInputAvFmtCtx->streams[mVIdx];
AVCodecParameters *vCodecPar = vStream->codecpar;
if (NULL == vCodecPar) {
printf("%s failed (vCodecPar is null)\n", __func__);
return false;
}
if (AV_CODEC_ID_MJPEG == vCodecPar->codec_id) {
printf("video codec is MJPEG\n");
} else if (AV_CODEC_ID_H264 == vCodecPar->codec_id) {
printf("video codec is H264\n");
} else if (AV_CODEC_ID_HEVC == vCodecPar->codec_id) {
printf("video codec is H265\n");
} else {
printf("video codec is 0x%x\n", vCodecPar->codec_id);
}
mWidth = vCodecPar->width;
mHeight = vCodecPar->height;
AVRational avgFps = vStream->avg_frame_rate;
mFps = av_q2d(avgFps);
printf("%s (w %d, h %d, fps %lf)\n", __func__, mWidth, mHeight, mFps);
AVRational tb = vStream->time_base;
printf("%s (time base:%lf, start time:%lld)\n", __func__, av_q2d(tb), vStream->start_time);
return true;
}
bool FFRecordMP4::getInputAudioInfo(void) {
if (NULL == mInputAvFmtCtx) {
printf("%s failed (mAvFmtCtx is null)\n", __func__);
return false;
}
if (mAIdx < 0) {
printf("%s failed (no audio stream)\n", __func__);
return false;
}
AVStream *aStream = mInputAvFmtCtx->streams[mAIdx];
AVCodecParameters *aCodecPar = aStream->codecpar;
if (NULL == aCodecPar) {
printf("%s failed (aCodecPar is null)\n", __func__);
return false;
}
if ((aCodecPar->codec_id >= AV_CODEC_ID_FIRST_AUDIO)
&& (aCodecPar->codec_id <= AV_CODEC_ID_PCM_F24LE)) {
printf("audio codec is PCM\n");
} else if (AV_CODEC_ID_MP3 == aCodecPar->codec_id) {
printf("audio codec is MP3\n");
} else if (AV_CODEC_ID_AAC == aCodecPar->codec_id) {
printf("audio codec is AAC\n");
} else {
printf("audio codec is 0x%x\n", aCodecPar->codec_id);
}
mSampleRate = aCodecPar->sample_rate;
printf("%s audio sample rate:%d\n", __func__, mSampleRate);
return true;
}
void *FFRecordMP4::threadRun(void *data) {
if (data) {
FFRecordMP4 *record = (FFRecordMP4 *)data;
printf("create thread FFRecordMP4\n");
while (record->getStarted()) {
record->threadLoop();
}
}
return NULL;
}
// 不停地调用av_read_frame接收数据包, 数据包类型分视频和音频, 如果av_read_frame返回-1表示断开连接或流结束, 要退出线程
// 对于MP4容器, 对混合进去的视频和音频的编码格式是有要求的, 视频可以是MPEG4/H264, 音频一般是AAC
void FFRecordMP4::threadLoop(void) {
int ret = 0;
ret = av_read_frame(mInputAvFmtCtx, &mPacket);
if (ret >= 0) {
AVStream *in_stream = mInputAvFmtCtx->streams[mPacket.stream_index];
AVStream *out_stream = mOutputAvFmtCtx->streams[mPacket.stream_index];
// 写文件前必须对时间戳修改一下
// 因为收到的数据包的时间戳基线跟FFmpeg录制容器中的时间戳基线是不一样的, 这个时间戳基线也叫做时钟频率
mPacket.pts = av_rescale_q_rnd(mPacket.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
mPacket.dts = av_rescale_q_rnd(mPacket.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
mPacket.duration = av_rescale_q(mPacket.duration, in_stream->time_base, out_stream->time_base);
mPacket.pos = -1;
if((in_stream->codec->codec_type != AVMEDIA_TYPE_VIDEO) && (in_stream->codec->codec_type != AVMEDIA_TYPE_AUDIO)) {
return;
}
// video case
if (-1 != mVIdx && mPacket.stream_index == mVIdx) {
mVideoFramesNum++;
// ffmpeg读到的mPacket.data中没有0x00000001的分隔符
// 需要 av_bitstream_filter_filter 来转换一下
av_bitstream_filter_filter(mH264bsfc, mInputAvFmtCtx->streams[mVIdx]->codec, NULL, &mPacket.data, &mPacket.size, mPacket.data, mPacket.size, 0);
//printf("Frame time: %lf", ((mPacket.pts) * av_q2d(in_stream->time_base)));
// write the compressed frame to the output format
int nError = av_interleaved_write_frame(mOutputAvFmtCtx, &mPacket);
if (nError != 0)
{
char tmpBuf[AV_ERROR_MAX_STRING_SIZE] = {0};
printf("While writing video frame, %s\n", av_make_error_string(tmpBuf, sizeof(tmpBuf), nError));
}
}
// audio case
if (-1 != mAIdx && mPacket.stream_index == mAIdx) {
// write the compressed frame to the output format
int nError = av_interleaved_write_frame(mOutputAvFmtCtx, &mPacket);
if (nError != 0)
{
char tmpBuf[AV_ERROR_MAX_STRING_SIZE] = {0};
printf("While writing audio frame, %s\n", av_make_error_string(tmpBuf, sizeof(tmpBuf), nError));
}
}
// av_read_frame would allocate memory on mPacket.data
av_packet_unref(&mPacket);
//av_usleep(20000); // 20ms
} else {
av_usleep(10000); // 10ms
if(ret == AVERROR_EOF) {
printf("END OF FILE, mVideoFramesNum = %d\n", mVideoFramesNum);
mVideoFramesNum = 0;
} else {
printf("av_read_frame() got error:%d\n", ret);
}
setStarted(false);
}
}
int main(int argc, char *argv[])
{
bool ret = false;
pthread_t mThread;
FFRecordMP4 *pRecord = new FFRecordMP4();
ret = pRecord->openInputStream("http://192.168.199.160:7001/1/dd54fd96-8396-5faa-9cd4-0338ec1373e0.mp4");
if(!ret) {
return 1;
}
ret = pRecord->openOutputStream("/nfsroot/record.mp4");
if(!ret) {
return 1;
}
pRecord->getInputVideoInfo();
pRecord->getInputAudioInfo();
pRecord->setStarted(true);
if (pthread_create(&mThread, NULL, pRecord->threadRun, pRecord) != 0) {
printf("createThread thread failed!\n");
return -1;
}
sleep(1);
void *joinRes;
pthread_join(mThread, &joinRes);
pRecord->closeInputStream();
pRecord->closeOutputStream();
return 0;
}
编译
arm-linux-g++ ffrecord.cpp -I/ffmpeg/include -L/ffmpeg/lib -lpthread -lavdevice -lavformat -lavcodec -lavutil
运行
av_find_best_stream (v 0, a 1)
Input #0, mov,mp4,m4a,3gp,3g2,mj2, from 'http://192.168.199.160:7001/1/dd54fd96-8396-5faa-9cd4-0338ec1373e0.mp4':
Metadata:
major_brand : isom
minor_version : 512
compatible_brands: isomiso2avc1mp41letv
encoder : Lavf56.15.102
Duration: 00:03:56.70, start: 0.000000, bitrate: 3153 kb/s
Stream #0:0(und): Video: h264 (avc1 / 0x31637661), yuv420p, 1920x1072 [SAR 134:135 DAR 16:9], 3014 kb/s, 25 fps, 25 tbr, 12800 tbn, 50 tbc (default)
Metadata:
handler_name : VideoHandler
Stream #0:1(und): Audio: aac (mp4a / 0x6134706D), 44100 Hz, stereo, fltp, 128 kb/s (default)
Metadata:
handler_name : SoundHandler
codec id: 28, URL: http://192.168.199.160:7001/1/dd54fd96-8396-5faa-9cd4-0338ec1373e0.mp4
video stream index: 0, width: 1920, height: 1072, FrameRate: 25.00
codec id: 86018, URL: http://192.168.199.160:7001/1/dd54fd96-8396-5faa-9cd4-0338ec1373e0.mp4
audio stream index: 1
Output #0, mp4, to '/nfsroot/record.mp4':
Stream #0:0: Unknown: none
Stream #0:1: Unknown: none
[mp4 @ 0x871f50] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
[mp4 @ 0x871f50] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.
[mp4 @ 0x871f50] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
[mp4 @ 0x871f50] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.
video codec is H264
getInputVideoInfo (w 1920, h 1072, fps 25.000000)
getInputVideoInfo (time base:0.000078, start time:0)
audio codec is AAC
getInputAudioInfo audio sample rate:44100
create thread FFRecordMP4
END OF FILE, mVideoFramesNum = 5915
参考
https://blog.csdn.net/toshiba689/article/details/79426680