实现录屏功能,需要将Android设备通过Socket发送到客户端的AVPacket数据保存成文件。
在前面的文章中讲过界面如何通过OpenGL绘制AVPacket.
简单回顾下:Demuxer线程不断的读取视频socket链路上的数据,以AVPacket对象的形式交给解码器,解码器遍历PacketSink列表,PacketSink按照各自的实现使用AVPacket数据。我们继承PacketSink类,分别实现
RecoderVideoSink和RecoderAudioSink
class RecoderVideoSink:public PacketSink{
public:
RecoderVideoSink(RecorderThread *handler);
~RecoderVideoSink();
virtual bool open(AVCodecContext *ctx);
virtual void close();
virtual bool push(AVPacket *packet);
//virtual void disable();
private:
RecorderThread *recorder;
};
class RecoderAudioSink:public PacketSink{
public:
RecoderAudioSink(RecorderThread *handler);
~RecoderAudioSink();
virtual bool open(AVCodecContext *ctx);
virtual void close();
virtual bool push(AVPacket *packet);
//virtual void disable();
private:
RecorderThread *recorder;
};
将实现的这两个Sink,加入到Decoder的Sink列表,这样在解码过程中AVPacket会复制过来。
录屏功能新创建一个线程,线程的优先级可是设低一些,把宝贵的CPU资源优先让给其它线程。下面是部分代码
//创建待输出的文件
bool RecorderThread::openOutputFile()
{
const char *format_name = Utils::getFormatName(this->recordFormat);
assert(format_name);
const AVOutputFormat *format = findMuxer(format_name);
if (!format) {
qDebug()<<"Could not find muxer";
return false;
}
this->ctx = avformat_alloc_context();
if (!this->ctx) {
return false;
}
AVStream *stream = avformat_new_stream(this->ctx,videoCtx->codec);
if (!stream) {
return false;
}
int r = avcodec_parameters_from_context(stream->codecpar, videoCtx);
if (r < 0) {
return false;
}
this->videoStream.index = stream->index;
int ret = avio_open(&this->ctx->pb, this->fileName.toUtf8().data(),
AVIO_FLAG_WRITE);
if (ret < 0) {
qDebug()<<"Failed to open output file: "<<fileName;
avformat_free_context(this->ctx);
return false;
}
this->ctx->oformat = (AVOutputFormat *) format;
av_dict_set(&this->ctx->metadata, "comment",
"Power By Linkedbyte ",1.0);
qDebug()<<QString("Recording started to %1 file: %2").arg(format_name).arg(fileName);
return true;
}
//写入视频文件头信息
bool RecorderThread::processHeader()
{
this->mutex.lock();
while (!this->stopped && this->videoQueue.empty())
{
this->cond.wait(&this->mutex);
}
AVPacket *videoPkt = NULL;
if (!this->videoQueue.empty()) {
videoPkt = this->videoQueue.back();
this->videoQueue.pop();
}
AVPacket *audioPkt = NULL;
if (!this->audioQueue.empty()) {
audioPkt = this->audioQueue.back();
this->audioQueue.pop();
}
this->mutex.unlock();
int ret = false;
bool ok = false;
if (videoPkt) {
if (videoPkt->pts != AV_NOPTS_VALUE) {
qDebug()<<"The first video packet is not a config packet";
goto end;
}
assert(this->videoStream.index >= 0);
AVStream *vStream =
this->ctx->streams[this->videoStream.index];
ok = setExtradata(vStream, videoPkt);
if (!ok) {
goto end;
}
}
if (audioPkt) {
if (audioPkt->pts != AV_NOPTS_VALUE) {
goto end;
}
assert(this->audioStream.index >= 0);
AVStream *audio_stream =
this->ctx->streams[this->audioStream.index];
ok = setExtradata(audio_stream, audioPkt);
if (!ok) {
goto end;
}
}
ok = avformat_write_header(this->ctx, NULL) >= 0;
if (!ok) {
goto end;
}
ret = true;
end:
if (videoPkt) {
av_packet_free(&videoPkt);
}
if (audioPkt) {
av_packet_free(&audioPkt);
}
return ret;
}
效果图:
QQ 2276769057
https://github.com/linkedbyte/tomobile