How to create .webp image file from url in unity? - c#

I am developing app for Android, iOS.
I should download webp file from url to unity.
private IEnumerator StartWebPExample()
{
var lWebStream = new WWW(#"http://cdn.octo-dev.co.uk/octo/image01.webp");
yield return lWebStream;
Error lError;
Texture2D lTexture2D = Texture2DExt.CreateTexture2DFromWebP(lWebStream.bytes, true, true, out lError);
File.WriteAllBytes(Application.persistentDataPath + fileName, textureBytes);
Debug.Log("File Written On Disk!");
}
I tried for this operation but got this issue.

Assuming you are using this the CreateTexture2DFromWebP for some reason uses
lTexture2D.Apply(lMipmaps, true);
where Texture2D.Apply
makeNoLongerReadable: When set to true, Unity discards the copy of pixel data in CPU-addressable memory after this operation.
=> I would file this as a bug to the devs (I actually did - last maintenance is quite a while ago though - maybe one of the forks is actively being maintained) and for now modify the local source code to include a flag as parameter.

Related

Loading assets from Asset Bundles for Play Asset Delivery in Unity

I am currently maintaining an old project which I would very much like to keep the old code and avoid refactoring of the old code if it's possible. To upload it to google play store, I have used play asset delivery PAD system which is requiring asset bundle system.
I am able to successfully loading asset bundles in an asynchronous way with coroutines following the tutorials.
My question is that; is it possible to access bundled assets without changing original way of accessing such as
UnityEditor.AssetDatabase.LoadAssetAtPath("Assets/Prefabs/Game/Tiles/Tile.prefab", typeof(Tile)) as Tile;
instead of implementing the following code?
var assets = AssetDatabase.FindAssets("t:Sprite",
new[] {"Assets/Images"});
foreach (var guid in assets)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
Sprite loadedSprite = null;
if (imageDictionary.TryGetValue(assetPath, out loadedSprite)
== false)
{
loadedSprite = AssetDatabase.LoadAssetAtPath<Sprite>(
assetPath);
if (loadedSprite != null)
{
imageDictionary.Add(assetPath, loadedSprite);
AddImageToList(assetPath, loadedSprite);
}
}
}
I am currently using 2 asset bundles consists of texture and spine animation folders.I have created 2 asset bundles which are texture folder (110 MB) and spine animation bundle (23 MB) and base asset. When I created the aab file, file size increased to 370 MB.
(I know that I need to split my bundles to match play store requirement of 150 MB for base assets but this is a different issue.)
And also, my educated guess is that, unity is not only adding asset bundles but also adding assets in old fashioned way which is an issue might relate to this question.
I was also facing the same problem.
The initially built AAB file exceeded 150MB and could not be uploaded to the store.
But I found a way to upload.
order
Put the build scene in "File - BuildSetting - Scene In Build".
Check "Build App Bundle" in "File - BuildSetting".
Close BuildSetting and click "Google - Build Android App Bundle"
Build.
(In this process, I did not create an AssetBundle. I used the original program as it was)
Ignore the large size alarm in the middle of the build.
When the build is completed, the size will be larger than 150MB.
Don't worry about the size of the build file and upload it to the Google console.
"Google - Build Android App Bundle" The file built with this goes up.
I hope the problem is resolved.
#Alp, Google's PAD tutorial is quite limited and it only guesses you're trying to access textures, which might not be always the case.
You can actually access the bundles directly, and load them into memory, and if you've packed your prefab as an assetBundle, when you load your assetBundle this way you can just cast it to a GameObject later and then use the prefab as a GameObject to instantiate it or do whatever you need.
There is only one official way of loading asset bundles if you packed them as install-time,
public static IEnumerator LoadAssetBundleFromMemoryAsync(string packName, string assetName, Action<AssetBundle> callback)
{
var packRequest = PlayAssetDelivery.RetrieveAssetPackAsync(packName);
while (!packRequest.IsDone)
{
yield return null;
}
AssetLocation location = packRequest.GetAssetLocation(assetName);
long size = (long)location.Size;
long offset = (long)location.Offset;
using (Stream stream = File.OpenRead(location.Path))
{
byte[] bytes = new byte[size];
stream.Seek(offset, SeekOrigin.Begin);
stream.Read(bytes, 0, bytes.Length);
AssetBundleCreateRequest request = AssetBundle.LoadFromMemoryAsync(bytes);
yield return request;
callback(request.assetBundle);
}
}
And then, I also figured out an unofficial way of loading asset bundles directly from file. It turns out that the location we get for a given assetBundle is going to be inside the split apk module with the same name as the assetPack.
But if we just replace the split_pack.apk with base.apk, we can actually read it directly from the File without needing to actually pre-load the bundle into memory. Load from memory will put it into memory in advance, while using the LoadFromFile way, you can actually load the bundle to memory when it's needed.
Something like this:
public static IEnumerator LoadAssetBundleFromFileAsync (string packName, string assetName, Action<AssetBundle> callback)
{
var packRequest = PlayAssetDelivery.RetrieveAssetPackAsync(packName);
while (!packRequest.IsDone)
{
yield return null;
}
AssetLocation location = packRequest.GetAssetLocation(assetName);
string path = location.Path.Replace($"split_{packName}.apk", "base.apk");
string basePath = $"{path}!assets/assetpack/{assetName}";
AssetBundleCreateRequest request = AssetBundle.LoadFromFileAsync(basePath);
while (!request.isDone)
{
yield return null;
}
callback(request.assetBundle);
}
You could also maybe only return the 'assetPath' and load it later.
These are only for install-time asset packs.
If you're trying to load any file from a fast-follow or on-demand asset pack, then all you need is the location.Path to load it from, as a raw file.
public static IEnumerator GetAssetLocationFromAssetPack (string packName, string assetName, Action<string> callback)
{
PlayAssetPackRequest packRequest = PlayAssetDelivery.RetrieveAssetPackAsync(packName);
while (!packRequest.IsDone)
{
yield return null;
}
AssetLocation location = packRequest.GetAssetLocation(assetName);
callback(location.Path);
}
I hope it helps, and be wary that any folder or assets contained in your StreamingAssets if also set to be in an assetPack will be added in double, everything contained in StreamingAssets will be in your base.apk (within the AAB), so if you have something from StreamingAssets that will actually be loaded from a pack, delete it from Streaming to avoid an AAB bigger than expected.

Application not saving png file on phone - unity

I am making a camera app on unity for learning purpose. I followed two tutorial and got my cam running Now here is a code that I found on stack overflow, The issue is its not saving the output on phone.
public void Pic()
{
StartCoroutine(TakePhoto());
}
IEnumerator TakePhoto() // Start this Coroutine on some button click
{
// NOTE - you almost certainly have to do this here:
yield return new WaitForEndOfFrame();
Texture2D photo = new Texture2D(backCam.width, backCam.height);
photo.SetPixels(backCam.GetPixels());
photo.Apply();
//Encode to a PNG
byte[] bytes = photo.EncodeToPNG();
//Write out the PNG. Of course you have to substitute your_path for something sensible
File.WriteAllBytes(Application.dataPath + "photo.png", bytes);
}
I have attached Pic() to a button.
I am just learning these so might be making some stupid mistake.
It needs to be /photo.png, otherwise you are just appending a filename to foldername
You should also consider switching to persistentDataPath and validating it for file io (checking if it exists is a good practice)
For Paths you should always use Path.Combine instead of direct string concatenation
Path.Combine(Application.dataPath, "photo.png")
It is also possible that the folder or file simply don't exist yet so you could check that and create them in that case before writing
var filePath = Path.Combine(Application.dataPath, "photo.png");
if (!Directory.Exists(Application.dataPath))
{
Directory.CreateDirectory(Application.dataPath);
}
if (!File.Exists(filePath))
{
File.Create(filePath);
}
//Write out the PNG. Of course you have to substitute your_path for something sensible
File.WriteAllBytes(filePath, bytes);
You need to add a slash before your file name.
File.WriteAllBytes($"{Application.dataPath}/photo.png", bytes);

CSCore crashes when changing sample rate and writing to file

I'm trying to use the CSCore .net library to convert sound files to a specific format for another process. I'm using it in Unity, but I don't think that's specific to the problem as it otherwise works perfectly.
I've got this code right now:
using (IWaveSource source = CodecFactory.Instance.GetCodec(audioPath)) {
using (IWaveSource destination = source.ToSampleSource()
.ChangeSampleRate(16000)
.ToMono()
.ToWaveSource(16)) {
audioPath = Application.dataPath + "/" + Path.GetFileNameWithoutExtension(audioPath) + "_temp_converted" + Path.GetExtension(audioPath);
destination.WriteToFile(audioPath);
}
}
It seems to be the combination of changing the sample rate and writing the file that causes the crash. If I remove the .ChangeSampleRate line (or replace 16000 with the current sample rate of the file) then it saves a mono 16-bit .wav file fine, and if I keep that line but don't try to write it to a file, Unity doesn't crash.
Has anyone else experienced this, or have any insight into what might be causing it? I'm starting to tear my hair out a bit with this!
Thanks.

ExpressionPlayer does not play Azure Smooth Streaming Source

I'm writing a VOD solution. For some time I have been working with the SSME:SmoothStreamingMediaElement successfully for testing and now I would like to utilise one of the Expression Players.
I'm using Azure Media Services, specifically Smooth Streaming. While these work fine in SSME I can't get them to work with an ExpressionPlayer. I don't know why.
I'm now at a point where I'm hard coding a Uri to try and get this to work as below:
void dataConectorPopulatePlaylistDownloadComplete(MemoryStream returnData, EventArgs e)
{
<snip>
var myPlaylist = new ExpressionMediaPlayer.Playlist();
var playlistItem = new PlaylistItem();
playlistItem.MediaSource = new Uri("http://xxxxxms1.origin.mediaservices.windows.net/b78750fc-9e2f-448c-86e3-d5de084791ea/GOPR0009.MP4-b2d2b578-3560-42c6-9927-2a791f395e19.ism/manifest",UriKind.Absolute);
playlistItem.IsAdaptiveStreaming = true;
myPlaylist.Items.Add(playlistItem);
SmoothPlayerStreaming.Playlist = myPlaylist;
<snip>
}
Using the above returns 404 not found in the player video playback window.
This is a valid URL and a valid Smooth Streaming Uri. Using this exact same Uri in a SSME control works fine.
What have I done wrong?
The ExpressionMediaPlayer class makes a hidden call to the ClientBin/SmoothStreaming.xap file. If you don't have it there - you should add it.
Here is the link to the blog post where you can download the xap file and source code of the expression player. Direct link
After you download the archive above, you can find this file at this path: EE4SP1SilverlightDefaultWithAudioVolume.zip\Templates\Silverlight Default -- with Audio Volume On Start\SmoothStreaming.xap
If it still doesn't work, you should replace the MediaPlayer.dll by projects from the archive. You need to add (Add -> Existing Project) 3 projects from the SharedV4SP1 folder: MediaPlayer, OfflineShared, PlugInMSSCtrl.
I've already tested your code in my application and it started to work after I have copied the xap file and replaced the dll-reference by existing projects.

Getting MP4 File Duration with DirectShow

I need to get the duration of an mp4 file, preferably as a double in seconds. I was using DirectShow (see code below), but it keeps throwing a particularly unhelpful error. I'm wondering if someone has an easy solution to this. (Seriously, who knew that getting that information would be so difficult)
public static void getDuration(string moviePath)
{
FilgraphManager m_objFilterGraph = null;
m_objFilterGraph = new FilgraphManager();
m_objFilterGraph.RenderFile(moviePath);
IMediaPosition m_objMediaPosition = null;
m_objMediaPosition = m_objFilterGraph as IMediaPosition;
Console.WriteLine(m_objMediaPosition.Duration);
}
Whenever I run this code, I get the error: "Exception from HRESULT: 0x80040265"
I also tried using this: Getting length of video
but it doesn't work either because I don't think that it works on MP4 files.
Seriously, I feel like there has to be a much easier way to do this.
Note: I would prefer to avoid using exe's like ffmpeg and then parsing the output to get the information.
You are approaching the problem correctly. You need to build a good pipeline starting from source .MP4 file and up to video and audio renderers. Then IMediaPosition.Duration will get you what you want. Currently you are getting VFW_E_UNSUPPORTED_STREAM because you cannot build the pipeline.
Note that there is no good support for MPEG-4 in DirectShow in clean Windows, you need a third party parser installed to add missing blocks. This is the likely cause of your problem. There are good Free DirectShow Mpeg-4 Filters available to fill this gap.
The code sample under the link Getting length of video is basically valid too, however it uses deprecated component which in additional make additional assumptions onto the media file in question. Provided that there is support for .MP4 in the system, IMediaPosition.Duration is to give you what you look for.
You can use get_Duration() from IMediaPosition interface.
This return a double value with the video duration in seconds.
Double Lenght;
m_FilterGraph = new FilterGraph()
//Configure the FilterGraph()
m_mediaPosition = m_FilterGraph as IMediaPosition;
m_mediaPosition.get_Duration(out Length);
Using Windows Media Player Component also, we can get the duration of the video.
I hope that following code snippet may help you guys :
using WMPLib;
// ...
var player = new WindowsMediaPlayer();
var clip = player.newMedia(filePath);
Console.WriteLine(TimeSpan.FromSeconds(clip.duration));
and don't forget to add the reference of wmp.dll which will be
present in System32 folder.

Categories

Resources