jos*_*sch 5 audio concatenation ogg lossless vorbis
我正在尝试将多个ogg vorbis文件连接成一个.
我知道理论上它应该足够了:
cat 1.ogg 2.ogg > combined.ogg
Run Code Online (Sandbox Code Playgroud)
但这有缺点:
我不想放松质量,所以我可以将它们重新编码为像flac这样的无损格式,但这会让文件大小爆炸.
似乎没有工具可以做到这一点.例如,oggCat将重新编码音频,从而导致质量轻微下降,并且ffmpeg concat demuxer不适用于所有输入文件.我打开这个超级用户的问题来找到一个工具,但当我发现没有工具时写了我自己的工具.
所以我尝试使用libogg和libvorbis手动将输入文件中的ogg数据包连接到输出文件的ogg页面.假设是,所有ogg输入文件都使用完全相同的参数进行编码.
我想出了以下代码:
#include <ogg/ogg.h>
#include <vorbis/codec.h>
#include <stdio.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <time.h>
int read_page(int fd, ogg_sync_state *state, ogg_page *page)
{
int ret;
ssize_t bytes;
while(ogg_sync_pageout(state, page) != 1) {
char *buffer = ogg_sync_buffer(state, 4096);
if (buffer == NULL) {
fprintf(stderr, "ogg_sync_buffer failed\n");
return -1;
}
bytes = read(fd, buffer, 4096);
if (bytes == 0) {
return -1;
}
ret = ogg_sync_wrote(state, bytes);
if (ret != 0) {
fprintf(stderr, "ogg_sync_wrote failed\n");
return -1;
}
}
return 0;
}
int main(int argc, char *argv[])
{
int ret;
ogg_sync_state state;
ogg_page page;
int serial;
ogg_stream_state sstate;
bool found_bos;
ogg_packet packet;
int fd;
int i;
vorbis_info info;
vorbis_comment comment;
int vorbis_header_read;
ssize_t bytes;
ogg_stream_state out_stream;
ogg_page out_page;
if (argc < 2) {
fprintf(stderr, "usage: %s file.ogg\n", argv[0]);
return 1;
}
srand(time(NULL));
ogg_stream_init(&out_stream, rand());
// go through all input files
for (i = 1; i < argc; i++) {
vorbis_header_read = 0;
found_bos = false;
fd = open(argv[i], O_RDONLY);
if (fd < 0) {
fprintf(stderr, "cannot open %s\n", argv[1]);
return 1;
}
ret = ogg_sync_init(&state);
if (ret != 0) {
fprintf(stderr, "ogg_sync_init failed\n");
return 1;
}
vorbis_info_init(&info);
vorbis_comment_init(&comment);
// go through all ogg pages
while (read_page(fd, &state, &page) == 0) {
serial = ogg_page_serialno(&page);
if (ogg_page_bos(&page)) {
if (found_bos) {
fprintf(stderr, "cannot handle more than one stream\n");
return 1;
}
ret = ogg_stream_init(&sstate, serial);
if (ret != 0) {
fprintf(stderr, "ogg_stream_init failed\n");
return 1;
}
found_bos = true;
}
if (!found_bos) {
fprintf(stderr, "cannot continue without bos\n");
return 1;
}
ret = ogg_stream_pagein(&sstate, &page);
if (ret != 0) {
fprintf(stderr, "ogg_stream_pagein failed\n");
return 1;
}
// if this is the last page, then only write it if we are in the
// last file
if (ogg_page_eos(&page) && i != argc - 1) {
continue;
}
// go through all (hopefully vorbis) packets
while((ret = ogg_stream_packetout(&sstate, &packet)) != 0) {
if (ret != 1) {
fprintf(stderr, "ogg_stream_packetout failed\n");
return 1;
}
// test if this stream is vorbis
if (vorbis_header_read == 0) {
ret = vorbis_synthesis_idheader(&packet);
if (ret == 0) {
fprintf(stderr, "stream is not vorbis\n");
return 1;
}
}
// read exactly three vorbis headers
if (vorbis_header_read < 3) {
ret = vorbis_synthesis_headerin(&info, &comment, &packet);
if (ret != 0) {
fprintf(stderr, "vorbis_synthesis_headerin failed\n");
return 1;
}
// if this is the first file, copy the header packet to the
// output
if (i == 1) {
ret = ogg_stream_packetin(&out_stream, &packet);
if (ret != 0) {
fprintf(stderr, "ogg_stream_packetin failed\n");
return 1;
}
}
vorbis_header_read++;
continue;
}
// if this is the first file, write a page to the output
if (vorbis_header_read == 3 && i == 1) {
while ((ret = ogg_stream_flush(&out_stream, &out_page)) != 0) {
bytes = write(STDOUT_FILENO, out_page.header, out_page.header_len);
if (bytes != out_page.header_len) {
fprintf(stderr, "write failed\n");
return 1;
}
bytes = write(STDOUT_FILENO, out_page.body, out_page.body_len);
if (bytes != out_page.body_len) {
fprintf(stderr, "write failed\n");
return 1;
}
}
vorbis_header_read++;
}
ogg_stream_packetin(&out_stream, &packet);
do {
ret = ogg_stream_pageout(&out_stream, &out_page);
if (ret == 0) break;
bytes = write(STDOUT_FILENO, out_page.header, out_page.header_len);
if (bytes != out_page.header_len) {
fprintf(stderr, "write failed\n");
return 1;
}
bytes = write(STDOUT_FILENO, out_page.body, out_page.body_len);
if (bytes != out_page.body_len) {
fprintf(stderr, "write failed\n");
return 1;
}
} while (!ogg_page_eos(&out_page));
}
}
vorbis_info_clear(&info);
vorbis_comment_clear(&comment);
ret = ogg_sync_clear(&state);
if (ret != 0) {
fprintf(stderr, "ogg_sync_clear failed\n");
return 1;
}
ret = ogg_stream_clear(&sstate);
if (ret != 0) {
fprintf(stderr, "ogg_stream_clear failed\n");
return 1;
}
close(fd);
}
ogg_stream_clear(&out_stream);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
这几乎可以工作,但在vorbis流连接的点处插入几乎听不见的咔嗒声.
怎么做到这一点?
它可以完成吗?
这是一个有趣的...:)
如果您可以承受文件之间几毫秒的静默/偏差,只需在两个流之间放置几个静默数据包(我必须检查每个数据包中的确切位模式的规范,但这应该不难以确定您是否有权访问解码器的源代码)。
如果您无法承受静音/偏斜,您可能需要重新编码,因为唯一的其他选择是调整压缩数据以改变波形连接部分的斜率......
编辑
另一种选择是在文件连接点对 PCM 数据应用平滑算法。这做起来并不简单,但其想法是您希望文件之间的波形“平滑”。这就是我所拥有的一切了...
编辑2
需要明确的是,假设源文件使用相同的参数,问题的示例代码几乎可以完美运行。它缺少的一件事是一种防止接缝被听到的方法。我的建议是放入几个静音数据包就可以解决这个问题,但对于那些负担不起的人来说,可以(作为纯粹的猜测)考虑将接缝周围两个数据包的地板上的乘数减少一,以使接缝不太明显。