How do I merge/join MP3 files with C#? - c#

I have a library of different words/phrases, and in order to build sentences at present I add a combination of these phrases into a playlist to make a sentence. Unfortunately if the user is running CPU intensive applications (which most of my users are) there can be a lag of a few seconds mid-sentence (in between phrases).
In order to combat this I was thinking of an approach which will merge the right combination of MP3 files on the fly into an appropriate phrase, save this in the %temp% directory, and then play this 1 MP3 file which should overcome the problem I'm experiencing with gaps.
What is the easiest way to do this in C#? Is there an easy way to do this? The files are fairly small, 3-4 seconds long each, and a sentence can consist of 3-20ish phrases.

here's how you can concatenate MP3 files using NAudio:
public static void Combine(string[] inputFiles, Stream output)
{
foreach (string file in inputFiles)
{
Mp3FileReader reader = new Mp3FileReader(file);
if ((output.Position == 0) && (reader.Id3v2Tag != null))
{
output.Write(reader.Id3v2Tag.RawData, 0, reader.Id3v2Tag.RawData.Length);
}
Mp3Frame frame;
while ((frame = reader.ReadNextFrame()) != null)
{
output.Write(frame.RawData, 0, frame.RawData.Length);
}
}
}
see here for more info

MP3 files consist of "frames", that each represent a short snippet (I think around 25 ms) of audio.
So yes, you can just concatenate them without a problem.

As MP3s are a compressed audio source, I imagine that you can't just concatenate them into a single file without decoding each one first to the wave form that it would play. This may be quite intensive. Perhaps you could cheat by using a critical section when playing back your phrase so that the CPU is not stolen from you until the phrase was complete. This isn't necessarily playing nice with other threads but might work if your phrases are short.

On simple option is to shell to the command line:
copy /b *.mp3 c:\new.mp3
Better would be to concatenate the streams. That's been answered here:
What would be the fastest way to concatenate three files in C#?

Related

Fastest way to locate a line which contains a specific word in a large text file

I'm trying to locate a line which contains a specific text inside a large text file (18 MB), currently I'm using StreamReader to open the file and read it line by line checking if it contains the search string
while ((line = reader.ReadLine()) != null)
{
if (line.Contains("search string"))
{
//Do something with line
}
}
But unfortunately, because the file I'm using has more than 1 million records, this method is slow. What is the quickest way to achieve this?
In general, disk IO of this nature is just going to be slow. There is likely little you can do to improve over your current version in terms of performance, at least not without dramatically changing the format in which you store your data, or your hardware.
However, you could shorten the code and simplify it in terms of maintenance and readability:
var lines = File.ReadLines(filename).Where(l => l.Contains("search string"));
foreach(var line in lines)
{
// Do something here with line
}
Reading the entire file into memory causes the application to hang and is very slow, do you think there are any other alternative
If the main goal here is to prevent application hangs, you can do this in the background instead of in a UI thread. If you make your method async, this can become:
while ((line = await reader.ReadLineAsync()) != null)
{
if (line.Contains("search string"))
{
//Do something with line
}
}
This will likely make the total operation take longer, but not block your UI thread while the file access is occurring.
Get a hard drive with a faster read speed (moving to a solid state drive if you aren't already would likely help a lot).
Store the data across several files each on different physical drives. Search through those drives in parallel.
Use a RAID0 hard drive configuration. (This is sort of a special case of the previous approach.)
Create an index of the lines in the file that you can use to search for specific words. (Creating the index will be a lot more expensive than a single search, and will require a lot of disk space, but it will allow subsequent searches at much faster speeds.)

Getting mp3 file length

I am currently trying to write an Audio Player in C#. I am using BASS library to deal with playing music but now i have small problem with getting length of the song.
Well i have read BASS doc and found a way:
"All" i need to do is
int stream = Bass.BASS_StreamCreateFile(filepath,....);
int length = Bass.BASS_ChannelBytes2Seconds(stream, Bass.BASS_ChannelGetLength(stream));
And in most of cases i get valid length of song. And here the problem starts. As far as i know the stream creation operation is quite expensive (correct me if i am mistaken) and creating a stream only to get length of the song looks a little silly.
So my question is: Is there any other way to get it without creating steam file (not being so expensive). I will be thinking later about reading id3 tags. Is creating that stream "evil that must be done no matter what" and even if i would try to get it with other library it would do exactly the same thing?
You can use the Microsoft.WindowsAPICodePack.Shell:
using Microsoft.WindowsAPICodePack.Shell;
Then code like so:
string file = "myfile.mp3"
ShellFile so = ShellFile.FromFilePath(file);
double 100nanoseconds;
double.TryParse(so.Properties.System.Media.Duration.Value.ToString(), out 100nanoseconds);
There is a code project that could help you as well

video not playing completly after join operation

This following is a code to join two videos. When I run the program it joins two videos and puts joined video in a folder. The joined video size is correct as it should be.
But when I play the video it plays the first part of the video in WMP but when i play the video in VLC it plays the second part of video.
public void JoiningVideo()
{
string j = #"D:/test2";
string outputpath = #"D:/test3/beforeEventab1.wmv";
DirectoryInfo di = new DirectoryInfo(j);
FileStream fs;
fs = new FileStream(outputpath, FileMode.Append);
foreach (FileInfo fi in di.GetFiles(#"*.wmv"))
{
byte[] bytesource = System.IO.File.ReadAllBytes(fi.FullName);
fs.Write(bytesource, 0, bytesource.Length);
}
fs.Close();
}
You know that each video-file starts with something called "header" ?
This part of the file contains information about the length etc.
If you want to join to seperate video files, you have to merge the headers to a new one containing information about both (joined) parts and to make sure that both videos fit to each other. (*)
Otherwise the video is not a valid file.
Due to the differences of the decoders of WMP and VLC, one recognises the first and the other one recognises the second file.
You can be lucky of that the the programs even played this 'corrupt' file! ;)
Just ask a search engine about merge wmv for a solution that should work for you!
(*)
To merge two videos they need to have
The same format (e.g. Resolution, Framerate, Bitrate)
If this does not apply, at least one of them has to be converted to match the other video
The videos have to be 'glued' together, it is not sufficient to append one's data to the other one.
Each video starts with a header. This header has to be changed to comprise information about the new (joined) video.
Also the raw image data cannot be simply appended. Every image is like a piece of a puzzle fitting to the next image in the video. The transition is like a new piece of a puzzle that has to be created. It may even be necessary to change/reorder the whole second file in order to get a working transition.
I am not a specialist, but at leas I can tell you, that this procedure is different for each type (MPEG, WMV, ..) of video. The best approach is to use an existing library for this purpose.

appending and reading text file

Environment: Any .Net Framework welcomed.
I have a log file that gets written to 24/7.
I am trying to create an application that will read the log file and process the data.
What's the best way to read the log file efficiently? I imagine monitoring the file with something like FileSystemWatcher. But how do I make sure I don't read the same data once it's been processed by my application? Or say the application aborts for some unknown reason, how would it pick up where it left off last?
There's usually a header and footer around the payload that's in the log file. Maybe an id field in the content as well. Not sure yet though about the id field being there.
I also imagined maybe saving the lines read count somewhere to maybe use that as bookmark.
For obvious reasons reading the whole content of the file, as well as removing lines from the log files (after loading them into your application) is out of question.
What I can think of as a partial solution is having a small database (probable something much smaller than a full-blown MySQL/MS SQL/PostgreSQL instance) and populating table with what has been read from the log file. I am pretty sure that even if there is power cut off and then the machine is booted again, most of the relational databases should be able to restore it's state with ease. This solution requires some data that could be used to identify the row from the log file (for example: exact time of the action logged, machine on which the action has taken place etc.)
Well, you will have to figure out your magic for your particular case yourself. If you are going to use well-known text encoding it may be pretty simple thoght. Look toward System.IO.StreamReader and it's ReadLine(), DiscardBufferedData() methods and BaseStream property. You should be able to remember your last position in the file and rewind to that position later and start reading again, given that you are sure that file is only appended. There are other things to consider though and there is no single universal answer to this.
Just as a naive example (you may still need to adjust a lot to make it work):
static void Main(string[] args)
{
string filePath = #"c:\log.txt";
using (var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.Read))
{
using (var streamReader = new StreamReader(stream,Encoding.Unicode))
{
long pos = 0;
if (File.Exists(#"c:\log.txt.lastposition"))
{
string strPos = File.ReadAllText(#"c:\log.txt.lastposition");
pos = Convert.ToInt64(strPos);
}
streamReader.BaseStream.Seek(pos, SeekOrigin.Begin); // rewind to last set position.
streamReader.DiscardBufferedData(); // clearing buffer
for(;;)
{
string line = streamReader.ReadLine();
if( line==null) break;
ProcessLine(line);
}
// pretty sure when everything is read position is at the end of file.
File.WriteAllText(#"c:\log.txt.lastposition",streamReader.BaseStream.Position.ToString());
}
}
}
I think you will find the File.ReadLines(filename) function in conjuction with LINQ will be very handy for something like this. ReadAllLines() will load the entire text file into memory as a string[] array, but ReadLines will allow you to begin enumerating the lines immediately as it traverses through the file. This not only saves you time but keeps the memory usage very low as it is processing each line one at a time. Using statements are important because if this program is interrupted it will close the filestreams flushing the writer and saving unwritten content to the file. Then when it starts up it will skip all the files that are already read.
int readCount = File.ReadLines("readLogs.txt").Count();
using (FileStream readLogs = new FileStream("readLogs.txt", FileMode.Append))
using (StreamWriter writer = new StreamWriter(readLogs))
{
IEnumerable<string> lines = File.ReadLines(bigLogFile.txt).Skip(readCount);
foreach (string line in lines)
{
// do something with line or batch them if you need more than one
writer.WriteLine(line);
}
}
As MaciekTalaska mentioned, I would strongly recommend using a database if this is something written to 24/7 and will get quite large. File systems are simply not equipped to handle such volume and you will spend a lot of time trying to invent solutions where a database could do it in a breeze.
Is there a reason why it logs to a file? Files are great because they are simple to use and, being the lowest common denominator, there is relatively little that can go wrong. However, files are limited. As you say, there's no guarantee a write to the file will be complete when you read the file. Multiple applications writing to the log can interfere with each other. There is no easy sorting or filtering mechanism. Log files can grow very big very quickly and there's no easy way to move old events (say those more than 24 hours old) into separate files for backup and retention.
Instead, I would considering writing the logs to a database. The table structure can be very simple but you get the advantage of transactions (so you can extract or backup with ease) and search, sort and filter using an almost universally understood syntax. If you are worried about load spikes, use a message queue, like http://msdn.microsoft.com/en-us/library/ms190495.aspx for SQL Server.
To make the transition easier, consider using a logging framework like log4net. It abstracts much of this away from your code.
Another alternative is to use a system like syslog or, if you have multiple servers and a large volume of logs, flume. By moving the log files away the source computer, you can store them or inspect them on a different machine far more effectively. However, these are probably overkill for your current problem.

How to combine SoundEffectInstances into a new Sound File /mp3 or wav

I'm working on the new WindowsPhone platform. I have a few intances of a SoundEffectInstance that I would like to combine into a new single Sound file (either SoundEffectInstance, SoundEffect or MediaElement, it does not matter.) I then want to save that file as an mp3 to the phone.
How do I do that? Normally, I would try to send all the files to a bytearray but I'm not sure if that is the correct method here, or how to convert the bytearray into an MP3 format sound.
So for example I have SoundEffectInstance soudBackground, playing from 0 - 5 seconds. I then have SoundEffectInstance chime playing from 3 - 4 seconds, and SoundEffectInstance foreground playing from 3.5 to 7 seconds. I want to combine all these into a single mp3 file that lasts 7 seconds long.
There are two task that you are trying to accomplish here:
Combine several sound files into a single sound file
Save the resulting file as an MP3.
As far as I have found thus far you will have a good bit of challenges with item 2. To date I have not found a pure .Net MP3 encoder. All the ones I find rely on P/Invokes to native code (Which of course won't work on the phone).
As for combining the files, you don't want to treat them as a SoundEffectInstance. That class is only meant for playing and it abstracts most of the details of the sound file away. Instead you will need to treat the sound files as arrays of ints. I'm going to assume that the sample rate on all three sound files is the exact same and that these are 16-bit recordings. I am also going to assume that these wave files are recorded in mono. I'm keeping the scenario simple for now. You can extend upon it with stereo and various sample rates after you've mastered this simpler scenario.
The first 48 bytes of the wave files is nothing but header. Skip past that (for now) and read the contents of the wave files into their own arrays. Once they are all read we can start mixing them together. Ignoring the time differences in which you want to start playing these sounds if we wanted to start producing a sample that is the combined result of all three we could do it by adding the values in the sound file array together and writing that out to an array to hold our result. But there's a problem. 16-bit numbers can only go up to 32,767 (and down to -32,768). If the combined value of all three sounds were to go beyond these limits you'll get really bad distortion. The easiest (though not necessarily the best) way to handle this is to consider the maximum number of simultaneous sounds that will play and scale the values down accordingly. From the 3.5 second to 4 second mark you will have all three sounds playing. So we will scale by dividing by three. Another way is to sum up the sound samples using a data type that can go beyond this range and then normalizing the values back to this range when you are done mixing them together.
Let's define some parameters.
int SamplesPerSecond = 22000;
int ResultRecordingLength = 7;
short[] Sound01;
short[] Sound02;
short[] Sound03;
int[] ResultantSoundBuffer;
short[] ProcessedResultSoundBuffer;
//Insert code to populate sound array's here.
// Sound01.Length will equal 5.0*SamplesPerSecond
// Sound02.Length will equal 1.0*SamplesPerSecond
// Sound03.Length will equal 3.5*SamplesPerSecond
ResultantSound = new int[ResultRecordingLength*SamplesPerSecond];
Once you've got your sound files read and the array prepared for receiving the resulting file you can start rendering. There's several ways we could go about this. Here is one:
void InitResultArray(int[] resultArray)
{
for(int i=0;i<resultArray.Length;++i)
{
resultArray[i]=0;
}
}
void RenderSound(short[] sourceSound, int[] resultArray, double timeOffset)
{
int startIndex = (int)(timeOffset*SamplesPerSecond);
int readIndex = 0;
for(int readIndex=0;((readIndex<sourceSound.Length)&&(readIndex+sourceSound<resultArray.Length;++readIndex)
{
resultArray[readIndex+startIndex] += (int)sourceSound[readIndex];
}
}
RangeAdjust(int[] resultArray)
{
int max = int.MinimumValue;
int min = int.MaximumValue;
for(int i=0;i<resultArray;++i)
{
max = Math.Max(max, resultArray[i]);
min = Math.Min(min, resultArray[i]);
}
//I want the range normalized to [-32,768..32,768]
//you may want to normalize differently.
double scale = 65536d/(double)(max-min);
double offset = 32767-(max*scale);
for(int i=0;i<resultArray.Length;++i)
{
resultArray[i]= (scale*resultArray[i])+offset;
}
}
You would call InitResultAttay to ensure the result array is filled with zeros (I believe it is by default, but I still prefer to explicitly set it to zero) and then call RenderSound() for each sound that you want in your result. After you've rendered your sounds call RangeAdjust to normalize the sound. All that's left is to write it to a file. You'll need to convert from ints back to shorts.
short[] writeBuffer = new short[ResultantSound.Length];
for(int i=0;i<writeBuffer.Length;++i)
writeBuffer[i]=(short)ResultantSound[i];
Now the mixed sound is all ready to write to the file. There is just one thing missing, you need to write the 48 byte wave header before writing the file. I've written code on how to do that here: http://www.codeproject.com/KB/windows-phone-7/WpVoiceMemo.aspx

Categories

Resources