众所周知,B站客户端下载的视频不能直接在相册找到,它隐藏在/storage/emulated/0/Android/data/tv.danmaku.bili/download下,格式也变成了m4s,甚至视频跟音频文件也被分开存放,从而仅仅只能在B站客户端对缓存的视频观看,在社区里也有很多人做的程序,但今天兴致一起就想自己来来写一个程序。
原理
我们进入download目录下,发现里面有一个个数字组成目录组成

,在随便进入一个子目录,下级文件就会变成由c_数字组成的文件夹
,我们在进入这个子文件夹,该文件夹里面包含了一个文件夹和两个

文件(entry.json和danmaku.json和一个数字命名的文件夹),

其中,我们需要重点关注一个entry.josn的文件,Entry,在中文当中是入口的意思,让我们打开入口,这个文件包含了不少信息,包含了这个视频的名字,缓存文件的大小,视频的时长,视频的分辨率等很重要的信息,我们现在重点关注一个是type_tag的标签,他这个标签的值与entry.json同目录下的文件夹名称是一样的,通过读取这个值,我们可以很方便的定位视频与音频所在的目录(至于为什么不关注其他值相同的标签,是因为作者当前只验证了这一个值的doge) 所以我们现在就需要读取这个josn文件,有很多的开源库都可以读取josn,我们这里使用了[cJSON](https://github.com/DaveGamble/cJSON)这个库,通过cJSON读取相应的标签,在按照标签上的直径五相应的文件夹,(例如80),

B站将视频文件与音频文件放在不同的文件下,现在我们调用ffmpeg的库来将视频与音频合并,
int merge_audio_video(const char* audio_file, const char* video_file, const char* output_file) {
AVFormatContext* input_format_ctx_audio = NULL, * input_format_ctx_video = NULL, * output_format_ctx = NULL;
AVOutputFormat* output_format = NULL;
AVStream* audio_stream = NULL, * video_stream = NULL, * out_audio_stream = NULL, * out_video_stream = NULL;
AVPacket packet;
int ret;
double frame_rate;
// 获取视频帧速率
frame_rate = get_frame_rate(video_file);
if (frame_rate == 0.0) {
fprintf(stderr, "无法获取视频帧速率。\n");
return -1;
}
// 打开输入文件
if ((ret = avformat_open_input(&input_format_ctx_audio, audio_file, 0, 0)) < 0) {
fprintf(stderr, "无法打开输入音频文件。\n");
return ret;
}
if ((ret = avformat_open_input(&input_format_ctx_video, video_file, 0, 0)) < 0) {
fprintf(stderr, "无法打开输入视频文件。\n");
return ret;
}
if ((ret = avformat_find_stream_info(input_format_ctx_audio, 0)) < 0) {
fprintf(stderr, "无法获取音频文件的流信息。\n");
return ret;
}
if ((ret = avformat_find_stream_info(input_format_ctx_video, 0)) < 0) {
fprintf(stderr, "无法获取视频文件的流信息。\n");
return ret;
}
avformat_alloc_output_context2(&output_format_ctx, NULL, NULL, output_file);
if (!output_format_ctx) {
fprintf(stderr, "无法创建输出上下文。\n");
return AVERROR_UNKNOWN;
}
output_format = output_format_ctx->oformat;
// 添加音频流
audio_stream = input_format_ctx_audio->streams[0];
out_audio_stream = avformat_new_stream(output_format_ctx, NULL);
if (!out_audio_stream) {
fprintf(stderr, "无法分配输出音频流。\n");
return AVERROR_UNKNOWN;
}
if ((ret = avcodec_parameters_copy(out_audio_stream->codecpar, audio_stream->codecpar)) < 0) {
fprintf(stderr, "无法复制音频编解码参数。\n");
return ret;
}
out_audio_stream->time_base = audio_stream->time_base;
// 添加视频流
video_stream = input_format_ctx_video->streams[0];
out_video_stream = avformat_new_stream(output_format_ctx, NULL);
if (!out_video_stream) {
fprintf(stderr, "无法分配输出视频流。\n");
return AVERROR_UNKNOWN;
}
if ((ret = avcodec_parameters_copy(out_video_stream->codecpar, video_stream->codecpar)) < 0) {
fprintf(stderr, "无法复制视频编解码参数。\n");
return ret;
}
out_video_stream->time_base = (AVRational){ 1, (int)frame_rate };
out_video_stream->codecpar->codec_tag = 0;
// 打开输出文件
if (!(output_format->flags & AVFMT_NOFILE)) {
if ((ret = avio_open(&output_format_ctx->pb, output_file, AVIO_FLAG_WRITE)) < 0) {
fprintf(stderr, "无法打开输出文件。\n");
return ret;
}
}
if ((ret = avformat_write_header(output_format_ctx, NULL)) < 0) {
fprintf(stderr, "打开输出文件时发生错误。\n");
return ret;
}
// 写入音频数据包
while (av_read_frame(input_format_ctx_audio, &packet) >= 0) {
packet.stream_index = out_audio_stream->index;
av_packet_rescale_ts(&packet, audio_stream->time_base, out_audio_stream->time_base);
av_interleaved_write_frame(output_format_ctx, &packet);
av_packet_unref(&packet);
}
// 写入视频数据包
while (av_read_frame(input_format_ctx_video, &packet) >= 0) {
packet.stream_index = out_video_stream->index;
av_packet_rescale_ts(&packet, video_stream->time_base, out_video_stream->time_base);
av_interleaved_write_frame(output_format_ctx, &packet);
av_packet_unref(&packet);
}
av_write_trailer(output_format_ctx);
avformat_close_input(&input_format_ctx_audio);
avformat_close_input(&input_format_ctx_video);
if (!(output_format->flags & AVFMT_NOFILE)) {
avio_closep(&output_format_ctx->pb);
}
avformat_free_context(output_format_ctx);
return 0;
}
我们直接调用了ffmpeg的库,将音频与视频结合,期间,在第一版代码中出了点小问题,视频在转换完成后会出现视频画面3s,音频120s的情况,后来,解决办法就是在转换之前先读取音频的时长,在转换的时候将视频帧拉长到与音频帧相同的长度。
Comments 1 条评论
Hello my loved one! I wish to say that this article is amazing, nice written and come with approximately all important infos.
I’d like to look extra posts like this .